Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions cmd/ateapi/internal/authn/authn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package authn

import (
"context"
"log/slog"

"github.com/agent-substrate/substrate/internal/principal"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/peer"
)

// Config for additional authentication methods. We can add different types of authentication, such as JWT token based authentication.
type Config struct {
}

func NewAuthenticationInterceptor(cfg *Config) grpc.UnaryServerInterceptor {
return newMTLSAuthenticationInterceptor
}

func hasPrincipal(ctx context.Context) (string, bool) {
p, ok := peer.FromContext(ctx)
if !ok || p.AuthInfo == nil {
slog.ErrorContext(ctx, "Authentication failed: no peer or auth info in context.")
return "", false
}

tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo)
if !ok {
slog.ErrorContext(ctx, "Authentication failed: no TLS info in context.")
return "", false
}

if len(tlsInfo.State.PeerCertificates) == 0 {
slog.ErrorContext(ctx, "Authentication failed: no peer certificates in TLS info.")
return "", false
}

clientCert := tlsInfo.State.PeerCertificates[0]
if len(clientCert.URIs) == 0 {
slog.ErrorContext(ctx, "Authentication failed: no URIs in peer certificate.")
return "", false
}

id := clientCert.URIs[0].String()
slog.InfoContext(ctx, "Authentication successful", slog.String("id", id))
return id, true
}

func newMTLSAuthenticationInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
pInfo := principal.PrincipalInfo{
ID: "anonymous",
}
if id, ok := hasPrincipal(ctx); ok {
pInfo = principal.PrincipalInfo{
ID: id,
}
}

newCtx := principal.InjectContext(ctx, pInfo)
return handler(newCtx, req)
}
10 changes: 8 additions & 2 deletions cmd/ateapi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import (
"os"
"time"

"github.com/agent-substrate/substrate/cmd/ateapi/internal/authn"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/controlapi"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/credbundle"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/sessionidentity"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store/ateredis"
"github.com/agent-substrate/substrate/internal/ateinterceptors"
"github.com/agent-substrate/substrate/internal/credbundle"
"github.com/agent-substrate/substrate/internal/serverboot"
"github.com/agent-substrate/substrate/internal/version"
"github.com/agent-substrate/substrate/pkg/client/clientset/versioned"
Expand Down Expand Up @@ -143,10 +144,15 @@ func main() {
serverboot.Fatal(ctx, "Failed to start listener", err)
}

authenticationInterceptor := authn.NewAuthenticationInterceptor(&authn.Config{})

mux := grpc.NewServer(
grpc.Creds(serverCreds),
grpc.StatsHandler(otelgrpc.NewServerHandler()),
grpc.UnaryInterceptor(ateinterceptors.ServerUnaryInterceptor),
grpc.ChainUnaryInterceptor(
authenticationInterceptor,
ateinterceptors.ServerUnaryInterceptor,
),
)
reflection.Register(mux)
ateapipb.RegisterControlServer(mux, sm)
Expand Down
3 changes: 2 additions & 1 deletion cmd/atenet/internal/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"os"

"github.com/agent-substrate/substrate/cmd/atenet/internal/router"
"github.com/agent-substrate/substrate/internal/version"
"github.com/spf13/cobra"
)
Expand All @@ -37,6 +38,6 @@ func Execute() {
}

func init() {
rootCmd.AddCommand(NewRouterCmd())
rootCmd.AddCommand(router.NewRouterCmd())
rootCmd.AddCommand(NewDnsCmd())
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package internal
package router

import (
"fmt"
"time"

"github.com/spf13/cobra"

"github.com/agent-substrate/substrate/cmd/atenet/internal/router"
)

func NewRouterCmd() *cobra.Command {
var cfg router.RouterConfig
var cfg routerConfig

cmd := &cobra.Command{
Use: "router",
Short: "Router components including xDS server and Envoy ExtProc gateway processing server",
RunE: func(cmd *cobra.Command, args []string) error {
srv, err := router.NewRouterServer(cfg)
srv, err := NewRouterServer(cfg)
if err != nil {
return fmt.Errorf("failed to create router server: %w", err)
}
Expand All @@ -46,6 +44,8 @@ func NewRouterCmd() *cobra.Command {
cmd.Flags().StringVar(&cfg.Namespace, "namespace", "default", "Target operations namespace")
cmd.Flags().StringVar(&cfg.Kubeconfig, "kubeconfig", "", "Absolute path to the kubeconfig configuration file")
cmd.Flags().StringVar(&cfg.AteapiAddr, "ateapi-address", "api.ate-system.svc:443", "gRPC host address of the cluster ateapi Control instance")
cmd.Flags().StringVar(&cfg.Auth.AteapiClientCertPath, "ateapi-client-cert", "/run/podidentity.podcert.ate.dev/credential-bundle.pem", "Path to the podidentity credential bundle the router presents as its client cert to ateapi.")
cmd.Flags().StringVar(&cfg.Auth.AteapiCACertsPath, "ateapi-ca-certs", "/run/servicedns-ca.podcert.ate.dev/trust-bundle.pem", "Path to the servicedns trust bundle used to verify ateapi's serving cert.")
cmd.Flags().IntVar(&cfg.HttpPort, "port-http", 8080, "TCP port for workload traffic entering through the Envoy Router")
cmd.Flags().IntVar(&cfg.XdsPort, "port-xds", 18000, "TCP port listening for the xDS dynamic Envoy connections")
cmd.Flags().IntVar(&cfg.ExtprocPort, "port-extproc", 50051, "Listen port for the Envoy dynamic External Processing (ext_proc) server")
Expand All @@ -55,7 +55,7 @@ func NewRouterCmd() *cobra.Command {
cmd.Flags().IntVar(&cfg.StatusPort, "status-port", 4040, "Port to serve /statusz on (set <= 0 to disable serving status)")
cmd.Flags().DurationVar(&cfg.HealthInterval, "health-interval", 1*time.Second, "Interval for checking health of dependent services")
cmd.Flags().IntVar(&cfg.HttpsPort, "port-https", 8443, "TCP port for HTTPS workload traffic entering through the Envoy Router")
cmd.Flags().StringVar(&cfg.EnvoyCertPath, "envoy-cert-path", "", "Path to the Envoy certificate file (if empty, a self-signed cert will be generated for testing)")
cmd.Flags().StringVar(&cfg.EnvoyCertPath, "envoy-cert-path", "", "Path to the Envoy certificate file.")

return cmd
}
83 changes: 83 additions & 0 deletions cmd/atenet/internal/router/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package router

import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"time"

"github.com/agent-substrate/substrate/internal/credbundle"
"google.golang.org/grpc/credentials"
)

type authConfig struct {
AteapiClientCertPath string
AteapiCACertsPath string
}

// routerConfig holds deployment setup and endpoint options for the router node instance.
type routerConfig struct {
Standalone bool
Namespace string
Kubeconfig string
AteapiAddr string
HttpPort int
XdsPort int
ExtprocPort int
ExtprocAddr string
EnvoyImage string
TemplatesFile string
StatusPort int
HealthInterval time.Duration
HttpsPort int
EnvoyCertPath string
LogLevel string
MetricsAddr string
Auth authConfig
}

// ateapiTransportCreds builds the TLS credentials the router uses to dial
// ateapi.
func (cfg *routerConfig) apiTransportCredentials() (credentials.TransportCredentials, error) {
tlsCfg, err := cfg.apiTLSConfig()
if err != nil {
return nil, err
}
return credentials.NewTLS(tlsCfg), nil
}

func (cfg *routerConfig) apiTLSConfig() (*tls.Config, error) {
caBytes, err := os.ReadFile(cfg.Auth.AteapiCACertsPath)
if err != nil {
return nil, fmt.Errorf("error reading ateapi CA certs: %w", err)
}
rootCAs := x509.NewCertPool()
if !rootCAs.AppendCertsFromPEM(caBytes) {
return nil, fmt.Errorf("parse ateapi CA certs from %s", cfg.Auth.AteapiCACertsPath)
}

if _, err := os.Stat(cfg.Auth.AteapiClientCertPath); err != nil {
return nil, fmt.Errorf("error reading ate apiserver client cert path from %q, error:%w",
cfg.Auth.AteapiClientCertPath, err)
}

return &tls.Config{
RootCAs: rootCAs,
GetClientCertificate: credbundle.ClientLoader(cfg.Auth.AteapiClientCertPath),
}, nil
}
4 changes: 2 additions & 2 deletions cmd/atenet/internal/router/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
type Controller struct {
k8sClient client.Client
clientset kubernetes.Interface
cfg RouterConfig
cfg routerConfig
xdsSrv *XdsServer
extprocSrv *ExtProcServer

Expand All @@ -39,7 +39,7 @@ type Controller struct {
func NewController(
k8sClient client.Client,
clientset kubernetes.Interface,
cfg RouterConfig,
cfg routerConfig,
xdsSrv *XdsServer,
extprocSrv *ExtProcServer,
) *Controller {
Expand Down
4 changes: 2 additions & 2 deletions cmd/atenet/internal/router/envoyrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ const (
// Envoy proxy instance running inside Kubernetes.
type envoyrunner struct {
k8sClient client.Client
cfg RouterConfig
cfg routerConfig
}

func newEnvoyRunner(k8sClient client.Client, cfg RouterConfig) *envoyrunner {
func newEnvoyRunner(k8sClient client.Client, cfg routerConfig) *envoyrunner {
return &envoyrunner{
k8sClient: k8sClient,
cfg: cfg,
Expand Down
4 changes: 2 additions & 2 deletions cmd/atenet/internal/router/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ type routerHealth struct {
interval time.Duration
clientset kubernetes.Interface
apiClient ateapipb.ControlClient
cfg RouterConfig
cfg routerConfig
}

func newRouterHealth(interval time.Duration, clientset kubernetes.Interface, apiClient ateapipb.ControlClient, cfg RouterConfig) *routerHealth {
func newRouterHealth(interval time.Duration, clientset kubernetes.Interface, apiClient ateapipb.ControlClient, cfg routerConfig) *routerHealth {
if interval <= 0 {
interval = time.Second
}
Expand Down
Loading
Loading