Skip to content
Draft
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
9 changes: 9 additions & 0 deletions api/network/v1alpha1/serviceroute_admission_envtest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ func TestTranslatedAPIAdmission(t *testing.T) {
p.Spec.Authorization = nil
}, false},
{"translated", func(*networkv1alpha1.ServiceRoutePolicy) {}, false},
{"persistent", func(p *networkv1alpha1.ServiceRoutePolicy) {
p.Spec.Authorization = &networkv1alpha1.ServiceRouteAuthorization{Persistent: true}
}, false},
{"persistent-with-deadline", func(p *networkv1alpha1.ServiceRoutePolicy) {
p.Spec.Authorization.Persistent = true
}, true},
{"missing-deadline", func(p *networkv1alpha1.ServiceRoutePolicy) {
p.Spec.Authorization = &networkv1alpha1.ServiceRouteAuthorization{}
}, true},
} {
t.Run(tt.name, func(t *testing.T) {
p := policy(tt.name)
Expand Down
13 changes: 9 additions & 4 deletions api/network/v1alpha1/serviceroute_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,7 @@ type ServiceRoutePolicySpec struct {
// +optional
Frontend *ServiceRouteFrontend `json:"frontend,omitempty"`

// Authorization bounds translated access independently of application leases.
// Only trusted networking integration may renew this deadline.
// Authorization defines the lifetime of translated service access.
// +optional
Authorization *ServiceRouteAuthorization `json:"authorization,omitempty"`

Expand All @@ -169,11 +168,17 @@ type ServiceRoutePolicySpec struct {
Region string `json:"region,omitempty"`
}

// ServiceRouteAuthorization bounds one network authorization episode.
// ServiceRouteAuthorization defines persistent or time-bounded access.
// +kubebuilder:validation:XValidation:rule="has(self.persistent) && self.persistent ? !has(self.validUntil) : has(self.validUntil)",message="persistent access must omit validUntil; bounded access requires validUntil"
type ServiceRouteAuthorization struct {
// Persistent retains access until policy withdrawal or VPC lifetime replacement.
// +optional
Persistent bool `json:"persistent,omitempty"`

// ValidUntil is the absolute UTC deadline for requests and existing replies.
// Node controllers reject deadlines more than two minutes in the future.
ValidUntil metav1.Time `json:"validUntil"`
// +optional
ValidUntil metav1.Time `json:"validUntil,omitempty"`
}

// ServiceRouteVPCReference identifies one immutable Cloud API VPC lifetime.
Expand Down
16 changes: 11 additions & 5 deletions config/crd/galactic.datumapis.com_serviceroutepolicies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,25 @@ spec:
type: object
x-kubernetes-map-type: atomic
authorization:
description: |-
Authorization bounds translated access independently of application leases.
Only trusted networking integration may renew this deadline.
description: Authorization defines the lifetime of translated service
access.
properties:
persistent:
description: Persistent retains access until policy withdrawal
or VPC lifetime replacement.
type: boolean
validUntil:
description: |-
ValidUntil is the absolute UTC deadline for requests and existing replies.
Node controllers reject deadlines more than two minutes in the future.
format: date-time
type: string
required:
- validUntil
type: object
x-kubernetes-validations:
- message: persistent access must omit validUntil; bounded access
requires validUntil
rule: 'has(self.persistent) && self.persistent ? !has(self.validUntil)
: has(self.validUntil)'
consumerVPCRef:
description: |-
ConsumerVPCRef pins authorization to the immutable lifetime of the VPC
Expand Down
5 changes: 3 additions & 2 deletions docs/api/network-gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ _Appears in:_



ServiceRouteAuthorization bounds one network authorization episode.
ServiceRouteAuthorization defines persistent or time-bounded access.



Expand All @@ -583,6 +583,7 @@ _Appears in:_

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `persistent` _boolean_ | Persistent retains access until policy withdrawal or VPC lifetime replacement. | | |
| `validUntil` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#time-v1-meta)_ | ValidUntil is the absolute UTC deadline for requests and existing replies.<br />Node controllers reject deadlines more than two minutes in the future. | | |


Expand Down Expand Up @@ -662,7 +663,7 @@ _Appears in:_
| `serviceRef` _[ServiceEndpointReference](#serviceendpointreference)_ | ServiceRef identifies the ServiceEndpoint in this namespace. | | Required: \{\} <br /> |
| `consumerVPCRef` _[ServiceRouteVPCReference](#serviceroutevpcreference)_ | ConsumerVPCRef pins authorization to the immutable lifetime of the VPC<br />selected by AttachmentSelector. Galactic resolves the name through the<br />Cloud API and requires both the live name and UID to match. | | |
| `frontend` _[ServiceRouteFrontend](#serviceroutefrontend)_ | Frontend is the address presented inside the consumer VPC. When set,<br />Galactic translates it to the ServiceEndpoint address and restores it on<br />replies. | | |
| `authorization` _[ServiceRouteAuthorization](#servicerouteauthorization)_ | Authorization bounds translated access independently of application leases.<br />Only trusted networking integration may renew this deadline. | | |
| `authorization` _[ServiceRouteAuthorization](#servicerouteauthorization)_ | Authorization defines the lifetime of translated service access. | | |
| `attachmentSelector` _[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#labelselector-v1-meta)_ | AttachmentSelector selects eligible VPC attachments by authoritative<br />labels. Labels that grant access to platform services must be assigned by<br />the network control plane, not by tenants. | | Required: \{\} <br /> |
| `protocolPorts` _[ServiceRouteProtocolPort](#servicerouteprotocolport) array_ | ProtocolPorts limits the traffic that the route is intended to carry.<br />An empty list means the endpoint's declared port and protocol are used. | | |
| `region` _string_ | Region limits selected attachments to a region. An empty value means the<br />policy applies in every region where the endpoint is available. | | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,8 @@ Service owners withdraw unhealthy producers. Networking integration deletes
access when the consumer or service is deleted. Galactic removes obsolete
programming and reconciles current lifetimes after restart. Recreated VPCs and
endpoints must not inherit previous grants. The current route policy has no
authorization lease; rollout requires a bounded revocation policy.
authorization policy. Persistent access requires explicit revocation; bounded
access requires a renewed deadline.

### Data plane

Expand Down Expand Up @@ -443,7 +444,7 @@ networking integration maps that frontend to an authorized service-side
destination for the network's DNS context. Galactic enforces the private path;
DNS interprets the destination and isolates zones and answers.

DNS contexts, record publication, health-aware discovery, and resolver leases
DNS contexts, record publication, health-aware discovery, and resolver access
belong to the [DNS component design](https://github.com/datum-cloud/dns-operator/blob/docs/internal-dns-architecture/docs/architecture/internal-dns/README.md).
The generic private-service path does not interpret DNS resources.

Expand Down
28 changes: 19 additions & 9 deletions docs/router/private-service-routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,25 @@ The publishing controller supplies the producer address in `ServiceEndpoint`;
`ServiceRoutePolicy.spec.frontend.address` supplies the consumer address.
Both addresses must use the same IP family. Ports are not translated.

A translated policy must pin the consumer VPC by name and UID and include an
`authorization.validUntil` deadline within the next two minutes. Selected
attachments must match that VPC's allocated network identity. Kernel checks
stop new requests and established replies when the authorization expires, even
if the controller or its API is unavailable. Renew authorization explicitly to
restore access.
A translated policy pins the consumer VPC by name and UID. Selected attachments
must match that VPC's allocated network identity. Persistent access remains
active during control plane outages and ends when the policy is withdrawn or
the VPC lifetime changes.

```yaml
spec:
authorization:
# Keep connectivity until an explicit policy or VPC lifetime change.
persistent: true
```

Time-bounded access sets `authorization.validUntil` within the next two minutes
and omits `persistent`. Kernel checks enforce its deadline on requests and
established replies. Persistent access must omit the deadline.

Policies claiming the same frontend address, protocol, and port on one consumer
attachment are rejected together. Separate VPC attachments can reuse that tuple.
The publisher owns authorization renewal and service health; Galactic enforces
The publisher owns access policy and service health; Galactic enforces
network access and forwards packets. It does not interpret DNS records or zones.

## Node programming reports
Expand All @@ -95,8 +104,9 @@ and input digest. It expires within 60 seconds or when authorization expires,
whichever comes first. Consumer and producer nodes report independently for
remote delivery.

Controllers using these reports must require the current topology digest and
all required nodes. `Accepted=True` means the policy is valid; it does not prove
Controllers require the current topology digest and all required nodes to
establish a new path. Expired reports indicate unknown observation health; they
do not revoke a persistent path. `Accepted=True` means the policy is valid; it does not prove
that forwarding is installed. Reports cover network programming, not the
health of the producer application. Startup withdraws previous local readiness
before rebuilding the node's state.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo/v2 v2.28.2 // indirect
github.com/orcaman/concurrent-map/v2 v2.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
Expand Down
13 changes: 1 addition & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps=
github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
Expand Down Expand Up @@ -198,24 +199,16 @@ github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWD
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/vishvananda/netlink v1.3.2-0.20261002201547-1d1c62507e3c h1:88GLXNKgc6pwIU/4yq+gleF5ISv3AsJkYmFlS1GjHvo=
github.com/vishvananda/netlink v1.3.2-0.20261002201547-1d1c62507e3c/go.mod h1:lEui7SPMd9fgxzHVGRAvTxsBGCF6PRH81o2kLWLWHgw=
github.com/vishvananda/netlink v1.3.2-0.20261015184244-3fdd74264153 h1:h+5tbko9K2pYcDASxNj0GYkEJnWmdtpF0GN7PSuY9oM=
github.com/vishvananda/netlink v1.3.2-0.20261015184244-3fdd74264153/go.mod h1:lEui7SPMd9fgxzHVGRAvTxsBGCF6PRH81o2kLWLWHgw=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
go.datum.net/compute v0.8.0 h1:/v1lni/oO4KwthPeXhhS0+VFRZjWYbU6CXiF3RgCMzY=
go.datum.net/compute v0.8.0/go.mod h1:u7YIQX4+Wgts5XJwtvlz5NMs23g7+R/4TkAmv3DRycw=
go.datum.net/compute v0.12.11 h1:bd/kcJMmuAP0pdLGV4OfOMKNg7GnYHVJwwJErYazBmE=
go.datum.net/compute v0.12.11/go.mod h1:OydYUbFT+r76BB02bmBOltJR2nLEy080qofvdqk1LFU=
go.datum.net/network-services-operator v0.27.0 h1:LYCUjc6i0/f3c5YXSgisnbV8gB3R8emDwNfiYapdCAo=
go.datum.net/network-services-operator v0.27.0/go.mod h1:9nuuBWdrkdnIBMaWJsWM3j4CcbKJIF1GDuCmAjdpIHo=
go.datum.net/network-services-operator v0.32.31 h1:OcUCPnAYj6ENiVyO4Wd24UeTvicJ/nSYkg2OQ5+quPk=
go.datum.net/network-services-operator v0.32.31/go.mod h1:7LfOP7awc5FZ8zID7wZEN8rQ/7qxyO3u+WIoIIA44os=
go.miloapis.com/ipam v0.4.0 h1:U+mg3RMFXj0c2eQ0Lm15CI0bYBPFDqy8IsGLN8v/eGs=
go.miloapis.com/ipam v0.4.0/go.mod h1:Jj7xg4lJi9psE0+4PuOg/GQOG8rG13h112xYoM994rc=
go.miloapis.com/ipam v0.5.1 h1:jb8jghj0jSOIDEPNo6b2gg2S4SWhNVfW4GDstb0zSKQ=
go.miloapis.com/ipam v0.5.1/go.mod h1:Jj7xg4lJi9psE0+4PuOg/GQOG8rG13h112xYoM994rc=
go.miloapis.com/locations v0.0.1 h1:voJKqBzyLX5x96M3+5y/ga7fgq9/+vypTizd+bBvqUY=
Expand Down Expand Up @@ -294,8 +287,6 @@ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc h1:rZHRz0ogq4Pid1IKtA/ivHi8XONVF5tqUQXA2TtgpkE=
google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:K/n0XLazCJHjD73YuMyNJsN38BPPy1kfFnKxUXaThFU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8 h1:0SXBqli5dnuCKMjI/X3FM226zEaEa0GUcSCJtN1vL2o=
google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20261019180522-ce908d29299b h1:6WNWHZk91dp4gZ25642OALkuG1UVNZbwENJD19DnB8k=
google.golang.org/genproto/googleapis/rpc v0.0.0-20261019180522-ce908d29299b/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc=
google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
Expand Down Expand Up @@ -327,8 +318,6 @@ sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9
sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw=
sigs.k8s.io/gateway-api v1.5.1 h1:RqVRIlkhLhUO8wOHKTLnTJA6o/1un4po4/6M1nRzdd0=
sigs.k8s.io/gateway-api v1.5.1/go.mod h1:GvCETiaMAlLym5CovLxGjS0NysqFk3+Yuq3/rh6QL2o=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/json v0.0.0-20260909141634-11ed52e25bc5 h1:k9jo7ED/fsfOcz3L1P20P5tjTNtsdQNwMOG2aKzHrl8=
sigs.k8s.io/json v0.0.0-20260909141634-11ed52e25bc5/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/knftables v0.0.18 h1:6Duvmu0s/HwGifKrtl6G3AyAPYlWiZqTgS8bkVMiyaE=
Expand Down
9 changes: 9 additions & 0 deletions internal/cni/netns.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ func addDefaultRouteIfMissing(handle *netlink.Handle, link netlink.Link, ifName
return fmt.Errorf("default route on %q already points via %s, refusing to add conflicting route via %s",
ifName, r.Gw, gateway)
}
routes, err := handle.RouteList(nil, addrFamily(gateway))
if err != nil {
return fmt.Errorf("list namespace routes: %w", err)
}
for _, route := range routes {
if isDefaultRouteDst(route.Dst) && route.LinkIndex != link.Attrs().Index {
return nil
}
}

// The on-link flag: a VPC gateway sits outside the prefix allocated to the
// attachment, for either family, so nothing on the interface tells the
Expand Down
57 changes: 57 additions & 0 deletions internal/cni/netns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,60 @@
t.Fatalf("default route gateway = %v, want %v", gw, testGateway)
}
}

func TestSecondaryInterfacePreservesPrimaryDefault(t *testing.T) {
netnsPath, cleanup := createTestNetnsWithDummy(t)
defer cleanup()
netns, err := ns.GetNS(netnsPath)
if err != nil {
t.Fatal(err)
}
defer netns.Close()

Check failure on line 487 in internal/cni/netns_test.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `netns.Close` is not checked (errcheck)
primaryGateway := net.ParseIP("fd00:99::1")
err = netns.Do(func(_ ns.NetNS) error {
handle, err := netlink.NewHandle()
if err != nil {
return err
}
defer handle.Close()

Check failure on line 494 in internal/cni/netns_test.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `handle.Close` is not checked (errcheck)
primary := &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: "primary"}}
if err := handle.LinkAdd(primary); err != nil {
return err
}
if err := handle.LinkSetUp(primary); err != nil {
return err
}
return handle.RouteAdd(&netlink.Route{Gw: primaryGateway, LinkIndex: primary.Attrs().Index, Flags: int(netlink.FLAG_ONLINK)})

Check failure on line 502 in internal/cni/netns_test.go

View workflow job for this annotation

GitHub Actions / Lint

The line is 127 characters long, which exceeds the maximum of 120 characters. (lll)
})
if err != nil {
t.Fatal(err)
}
for range 2 {
if err := configureInterfaceInNetns(netnsPath, "test-dummy", testIPNet, testGateway, nil, nil); err != nil {
t.Fatal(err)
}
}
if gateway := defaultRouteVia(t, netnsPath); gateway != nil {
t.Fatalf("secondary interface installed default route via %s", gateway)
}
err = netns.Do(func(_ ns.NetNS) error {
handle, err := netlink.NewHandle()
if err != nil {
return err
}
defer handle.Close()

Check failure on line 520 in internal/cni/netns_test.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `handle.Close` is not checked (errcheck)
routes, err := handle.RouteList(nil, netlink.FAMILY_V6)
if err != nil {
return err
}
for _, route := range routes {
if isDefaultRouteDst(route.Dst) && route.Gw.Equal(primaryGateway) {
return nil
}
}
return fmt.Errorf("primary default route is missing")

Check failure on line 530 in internal/cni/netns_test.go

View workflow job for this annotation

GitHub Actions / Lint

error-format: fmt.Errorf can be replaced with errors.New (perfsprint)
})
if err != nil {
t.Fatal(err)
}
}
8 changes: 7 additions & 1 deletion internal/controller/serviceroute_access.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ func (r *ServiceRoutePolicyReconciler) validateAuthorization(policy *networkv1al
return nil
}
if policy.Spec.Authorization == nil {
return errors.New("frontend translation requires authorization.validUntil")
return errors.New("frontend translation requires access authorization")
}
if policy.Spec.Authorization.Persistent {
if !policy.Spec.Authorization.ValidUntil.IsZero() {
return errors.New("persistent access must omit validUntil")
}
return nil
}
deadline := policy.Spec.Authorization.ValidUntil.Time
now := r.now()
Expand Down
56 changes: 56 additions & 0 deletions internal/controller/serviceroute_access_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,59 @@ func TestConflictingPoliciesFailClosedAndRecover(t *testing.T) {
})
}
}

func TestPersistentAuthorizationRequiresNoDeadline(t *testing.T) {
r := &ServiceRoutePolicyReconciler{}
p := &networkv1alpha1.ServiceRoutePolicy{Spec: networkv1alpha1.ServiceRoutePolicySpec{
Frontend: &networkv1alpha1.ServiceRouteFrontend{},
Authorization: &networkv1alpha1.ServiceRouteAuthorization{Persistent: true},
}}
if err := r.validateAuthorization(p); err != nil {
t.Fatal(err)
}
p.Spec.Authorization.ValidUntil = metav1.NewTime(time.Now().Add(time.Minute))
if err := r.validateAuthorization(p); err == nil {
t.Fatal("persistent authorization accepted a deadline")
}
}

func TestPersistentPathSurvivesControllerOutageAndWithdraws(t *testing.T) {
r, p, _ := pscFixture(t)
ctx := context.Background()
now := time.Now().UTC()
r.Now = func() time.Time { return now }
p.Spec.Authorization = &networkv1alpha1.ServiceRouteAuthorization{Persistent: true}
if err := r.Update(ctx, p); err != nil {
t.Fatal(err)
}
key := types.NamespacedName{Namespace: p.Namespace, Name: p.Name}
if _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: key}); err != nil {
t.Fatal(err)
}
now = now.Add(time.Hour)
if _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: key}); err != nil {
t.Fatal(err)
}
if len(r.Applied) == 0 {
t.Fatal("persistent path requires authorization renewal")
}
for _, intents := range r.Applied {
for _, intent := range intents {
if !intent.AuthorizationValidUntil.IsZero() {
t.Fatal("persistent path has a dataplane deadline")
}
}
}
if err := r.Get(ctx, key, p); err != nil {
t.Fatal(err)
}
if err := r.Delete(ctx, p); err != nil {
t.Fatal(err)
}
if _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: key}); err != nil {
t.Fatal(err)
}
if len(r.Applied) != 0 {
t.Fatal("withdrawn policy retained forwarding")
}
}
Loading
Loading