ce7cb702218693d08ea6d83464d2457c9250027c

This commit is contained in:
ms2k 2026-11-12 20:46:28 +09:00
commit 34c5f8963c
44 changed files with 145336 additions and 0 deletions

256
services/short/cloud/oci.go Normal file
View file

@ -0,0 +1,256 @@
package cloud
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"slices"
"sort"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/core"
"quorate.dev/services/short/config"
)
type Network interface {
ListCpes(context.Context, core.ListCpesRequest) (core.ListCpesResponse, error)
CreateCpe(context.Context, core.CreateCpeRequest) (core.CreateCpeResponse, error)
DeleteCpe(context.Context, core.DeleteCpeRequest) (core.DeleteCpeResponse, error)
ListIPSecConnections(context.Context, core.ListIPSecConnectionsRequest) (core.ListIPSecConnectionsResponse, error)
CreateIPSecConnection(context.Context, core.CreateIPSecConnectionRequest) (core.CreateIPSecConnectionResponse, error)
UpdateIPSecConnection(context.Context, core.UpdateIPSecConnectionRequest) (core.UpdateIPSecConnectionResponse, error)
DeleteIPSecConnection(context.Context, core.DeleteIPSecConnectionRequest) (core.DeleteIPSecConnectionResponse, error)
ListIPSecConnectionTunnels(context.Context, core.ListIPSecConnectionTunnelsRequest) (core.ListIPSecConnectionTunnelsResponse, error)
UpdateIPSecConnectionTunnel(context.Context, core.UpdateIPSecConnectionTunnelRequest) (core.UpdateIPSecConnectionTunnelResponse, error)
GetIPSecConnectionTunnelSharedSecret(context.Context, core.GetIPSecConnectionTunnelSharedSecretRequest) (core.GetIPSecConnectionTunnelSharedSecretResponse, error)
}
type Client struct {
API Network
Config config.Cloud
}
type State struct {
Bundle config.Bundle
Peers []string
Ready bool
}
func New(c config.Cloud, key string) (*Client, error) {
p := common.NewRawConfigurationProvider(c.Tenancy, c.User, c.Region, c.Fingerprint, key, nil)
api, err := core.NewVirtualNetworkClientWithConfigurationProvider(p)
if err != nil {
return nil, err
}
return &Client{API: &api, Config: c}, nil
}
func token(kind, drg, wan string) *string {
sum := sha256.Sum256([]byte(kind + ":" + drg + ":" + wan))
value := hex.EncodeToString(sum[:])
return &value
}
func create[T any](retry *string, call func(*string) (T, error)) (T, error) {
for {
result, err := call(retry)
service, ok := common.IsServiceError(err)
if !ok || service.GetHTTPStatusCode() != 409 || service.GetCode() != "InvalidatedRetryToken" {
return result, err
}
// Deterministic successors let another leader replay a create whose response was lost.
sum := sha256.Sum256([]byte(*retry))
retry = common.String(hex.EncodeToString(sum[:]))
}
}
func owned(tags map[string]string) bool { return tags["owner"] == "short" }
func (c *Client) Reconcile(ctx context.Context, wan string) (State, error) {
state := State{Bundle: config.Bundle{WAN: wan}}
var cpes []core.Cpe
var page *string
for {
r, err := c.API.ListCpes(ctx, core.ListCpesRequest{CompartmentId: &c.Config.Compartment, Page: page})
if err != nil {
return state, err
}
for _, p := range r.Items {
if owned(p.FreeformTags) {
cpes = append(cpes, p)
}
}
if r.OpcNextPage == nil {
break
}
page = r.OpcNextPage
}
var current *core.Cpe
for i := range cpes {
if *cpes[i].IpAddress == wan {
if current != nil {
return state, fmt.Errorf("duplicate owned CPE for WAN")
}
current = &cpes[i]
}
}
if current == nil {
r, err := create(token("cpe", c.Config.DRG, wan), func(retry *string) (core.CreateCpeResponse, error) {
return c.API.CreateCpe(ctx, core.CreateCpeRequest{OpcRetryToken: retry, CreateCpeDetails: core.CreateCpeDetails{
CompartmentId: &c.Config.Compartment, IpAddress: &wan, DisplayName: common.String("short"), FreeformTags: map[string]string{"owner": "short"},
}})
})
if err != nil {
return state, err
}
current = &r.Cpe
}
var connections []core.IpSecConnection
page = nil
for {
r, err := c.API.ListIPSecConnections(ctx, core.ListIPSecConnectionsRequest{CompartmentId: &c.Config.Compartment, DrgId: &c.Config.DRG, Page: page})
if err != nil {
return state, err
}
for _, p := range r.Items {
if owned(p.FreeformTags) {
connections = append(connections, p)
}
}
if r.OpcNextPage == nil {
break
}
page = r.OpcNextPage
}
var connection *core.IpSecConnection
for i := range connections {
p := &connections[i]
if *p.CpeId == *current.Id && p.LifecycleState != core.IpSecConnectionLifecycleStateTerminated && p.LifecycleState != core.IpSecConnectionLifecycleStateTerminating {
if connection != nil {
return state, fmt.Errorf("duplicate owned IPsec connection")
}
connection = p
}
}
if connection == nil {
r, err := create(token("ipsec", c.Config.DRG, wan), func(retry *string) (core.CreateIPSecConnectionResponse, error) {
return c.API.CreateIPSecConnection(ctx, core.CreateIPSecConnectionRequest{OpcRetryToken: retry, CreateIpSecConnectionDetails: core.CreateIpSecConnectionDetails{
CompartmentId: &c.Config.Compartment, CpeId: current.Id, DrgId: &c.Config.DRG, DisplayName: common.String("short"),
FreeformTags: map[string]string{"owner": "short"}, StaticRoutes: []string{c.Config.Address + "/32"},
CpeLocalIdentifier: &wan, CpeLocalIdentifierType: core.CreateIpSecConnectionDetailsCpeLocalIdentifierTypeIpAddress,
TunnelConfiguration: []core.CreateIpSecConnectionTunnelDetails{tunnel("short-0"), tunnel("short-1")},
}})
})
if err != nil {
return state, err
}
connection = &r.IpSecConnection
}
routes := []string{c.Config.Address + "/32"}
routeReady := slices.Equal(connection.StaticRoutes, routes)
if connection.LifecycleState == core.IpSecConnectionLifecycleStateAvailable && !routeReady {
if _, err := c.API.UpdateIPSecConnection(ctx, core.UpdateIPSecConnectionRequest{
IpscId: connection.Id, UpdateIpSecConnectionDetails: core.UpdateIpSecConnectionDetails{StaticRoutes: routes},
}); err != nil {
return state, err
}
}
allUp := false
for _, p := range append(connectionsWithout(connections, *connection.Id), *connection) {
if p.LifecycleState != core.IpSecConnectionLifecycleStateAvailable {
continue
}
r, err := c.API.ListIPSecConnectionTunnels(ctx, core.ListIPSecConnectionTunnelsRequest{IpscId: p.Id})
if err != nil {
return state, err
}
isCurrent := *p.Id == *connection.Id
up := len(r.Items) == 2
for _, head := range r.Items {
if head.VpnIp != nil {
state.Peers = append(state.Peers, *head.VpnIp+"/32")
}
if head.LifecycleState != core.IpSecConnectionTunnelLifecycleStateAvailable {
up = false
continue
}
if head.VpnIp == nil {
up = false
continue
}
if isCurrent && head.OracleCanInitiate != core.IpSecConnectionTunnelOracleCanInitiateInitiatorOrResponder {
_, err := c.API.UpdateIPSecConnectionTunnel(ctx, core.UpdateIPSecConnectionTunnelRequest{
IpscId: p.Id, TunnelId: head.Id,
UpdateIpSecConnectionTunnelDetails: core.UpdateIpSecConnectionTunnelDetails{
OracleInitiation: core.UpdateIpSecConnectionTunnelDetailsOracleInitiationInitiatorOrResponder,
},
})
if err != nil {
return state, err
}
}
if head.Status != core.IpSecConnectionTunnelStatusUp {
up = false
}
if isCurrent {
secret, err := c.API.GetIPSecConnectionTunnelSharedSecret(ctx, core.GetIPSecConnectionTunnelSharedSecretRequest{IpscId: p.Id, TunnelId: head.Id})
if err != nil {
return state, err
}
state.Bundle.Peers = append(state.Bundle.Peers, config.Peer{ID: *head.Id, Address: *head.VpnIp, Secret: *secret.SharedSecret})
}
}
if isCurrent {
allUp = up
}
}
sort.Slice(state.Bundle.Peers, func(i, j int) bool { return state.Bundle.Peers[i].ID < state.Bundle.Peers[j].ID })
sort.Strings(state.Peers)
state.Ready = len(state.Bundle.Peers) == 2 && routeReady
if state.Ready && allUp {
for _, p := range connections {
if *p.Id != *connection.Id && p.LifecycleState != core.IpSecConnectionLifecycleStateTerminating && p.LifecycleState != core.IpSecConnectionLifecycleStateTerminated {
if _, err := c.API.DeleteIPSecConnection(ctx, core.DeleteIPSecConnectionRequest{IpscId: p.Id}); err != nil {
return state, err
}
}
}
for _, p := range cpes {
if *p.Id == *current.Id {
continue
}
used := false
for _, conn := range connections {
if *conn.CpeId == *p.Id && conn.LifecycleState != core.IpSecConnectionLifecycleStateTerminated {
used = true
}
}
if !used {
if _, err := c.API.DeleteCpe(ctx, core.DeleteCpeRequest{CpeId: p.Id}); err != nil {
return state, err
}
}
}
}
return state, nil
}
func connectionsWithout(all []core.IpSecConnection, id string) []core.IpSecConnection {
var result []core.IpSecConnection
for _, p := range all {
if *p.Id != id {
result = append(result, p)
}
}
return result
}
func tunnel(name string) core.CreateIpSecConnectionTunnelDetails {
return core.CreateIpSecConnectionTunnelDetails{
DisplayName: &name, Routing: core.CreateIpSecConnectionTunnelDetailsRoutingStatic, IkeVersion: core.CreateIpSecConnectionTunnelDetailsIkeVersionV2,
OracleInitiation: core.CreateIpSecConnectionTunnelDetailsOracleInitiationInitiatorOrResponder, NatTranslationEnabled: core.CreateIpSecConnectionTunnelDetailsNatTranslationEnabledAuto,
PhaseOneConfig: &core.PhaseOneConfigDetails{IsCustomPhaseOneConfig: common.Bool(true), EncryptionAlgorithm: core.PhaseOneConfigDetailsEncryptionAlgorithm256Cbc, AuthenticationAlgorithm: core.PhaseOneConfigDetailsAuthenticationAlgorithmSha2256, DiffieHelmanGroup: core.PhaseOneConfigDetailsDiffieHelmanGroupGroup14, LifetimeInSeconds: common.Int(28800)},
PhaseTwoConfig: &core.PhaseTwoConfigDetails{IsCustomPhaseTwoConfig: common.Bool(true), EncryptionAlgorithm: core.PhaseTwoConfigDetailsEncryptionAlgorithm256Gcm, IsPfsEnabled: common.Bool(true), PfsDhGroup: core.PhaseTwoConfigDetailsPfsDhGroupGroup14, LifetimeInSeconds: common.Int(3600)},
DpdConfig: &core.DpdConfig{DpdMode: core.DpdConfigDpdModeInitiateAndRespond, DpdTimeoutInSec: common.Int(20)},
}
}

View file

@ -0,0 +1,383 @@
package cloud
import (
"context"
"errors"
"fmt"
"slices"
"testing"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/core"
"quorate.dev/services/short/config"
)
type networkFake struct {
Network
Cpes []core.Cpe
Connections []core.IpSecConnection
Heads map[string][]core.IpSecConnectionTunnel
Creates, Deletes, Updates int
RouteUpdates int
HoldRoutes bool
PendingRoutes []string
}
func (f *networkFake) ListCpes(context.Context, core.ListCpesRequest) (core.ListCpesResponse, error) {
return core.ListCpesResponse{Items: f.Cpes}, nil
}
func (f *networkFake) CreateCpe(_ context.Context, r core.CreateCpeRequest) (core.CreateCpeResponse, error) {
f.Creates++
p := core.Cpe{Id: common.String(fmt.Sprintf("cpe-%d", f.Creates)), IpAddress: r.IpAddress, FreeformTags: r.FreeformTags}
f.Cpes = append(f.Cpes, p)
return core.CreateCpeResponse{Cpe: p}, nil
}
func (f *networkFake) DeleteCpe(_ context.Context, r core.DeleteCpeRequest) (core.DeleteCpeResponse, error) {
f.Deletes++
var keep []core.Cpe
for _, p := range f.Cpes {
if *p.Id != *r.CpeId {
keep = append(keep, p)
}
}
f.Cpes = keep
return core.DeleteCpeResponse{}, nil
}
func (f *networkFake) ListIPSecConnections(context.Context, core.ListIPSecConnectionsRequest) (core.ListIPSecConnectionsResponse, error) {
return core.ListIPSecConnectionsResponse{Items: f.Connections}, nil
}
func (f *networkFake) CreateIPSecConnection(_ context.Context, r core.CreateIPSecConnectionRequest) (core.CreateIPSecConnectionResponse, error) {
f.Creates++
id := fmt.Sprintf("conn-%d", f.Creates)
p := core.IpSecConnection{Id: &id, CpeId: r.CpeId, DrgId: r.DrgId, FreeformTags: r.FreeformTags, StaticRoutes: r.StaticRoutes, CpeLocalIdentifier: r.CpeLocalIdentifier, LifecycleState: core.IpSecConnectionLifecycleStateAvailable}
f.Connections = append(f.Connections, p)
for i, head := range r.TunnelConfiguration {
f.Heads[id] = append(f.Heads[id], core.IpSecConnectionTunnel{Id: common.String(fmt.Sprintf("%s-%d", id, i)), VpnIp: common.String(fmt.Sprintf("8.8.%d.%d", f.Creates, i+1)), LifecycleState: core.IpSecConnectionTunnelLifecycleStateAvailable, Status: core.IpSecConnectionTunnelStatusDown, IkeVersion: core.IpSecConnectionTunnelIkeVersionEnum(head.IkeVersion), OracleCanInitiate: core.IpSecConnectionTunnelOracleCanInitiateEnum(head.OracleInitiation)})
}
return core.CreateIPSecConnectionResponse{IpSecConnection: p}, nil
}
func (f *networkFake) DeleteIPSecConnection(_ context.Context, r core.DeleteIPSecConnectionRequest) (core.DeleteIPSecConnectionResponse, error) {
f.Deletes++
var keep []core.IpSecConnection
for _, p := range f.Connections {
if *p.Id != *r.IpscId {
keep = append(keep, p)
}
}
f.Connections = keep
return core.DeleteIPSecConnectionResponse{}, nil
}
func (f *networkFake) UpdateIPSecConnection(_ context.Context, r core.UpdateIPSecConnectionRequest) (core.UpdateIPSecConnectionResponse, error) {
f.RouteUpdates++
f.PendingRoutes = slices.Clone(r.StaticRoutes)
for i, p := range f.Connections {
if *p.Id == *r.IpscId {
p.StaticRoutes = slices.Clone(r.StaticRoutes)
if !f.HoldRoutes {
f.Connections[i] = p
}
return core.UpdateIPSecConnectionResponse{IpSecConnection: p}, nil
}
}
return core.UpdateIPSecConnectionResponse{}, fmt.Errorf("unknown connection")
}
func (f *networkFake) ListIPSecConnectionTunnels(_ context.Context, r core.ListIPSecConnectionTunnelsRequest) (core.ListIPSecConnectionTunnelsResponse, error) {
return core.ListIPSecConnectionTunnelsResponse{Items: f.Heads[*r.IpscId]}, nil
}
func (f *networkFake) UpdateIPSecConnectionTunnel(_ context.Context, r core.UpdateIPSecConnectionTunnelRequest) (core.UpdateIPSecConnectionTunnelResponse, error) {
f.Updates++
for i := range f.Heads[*r.IpscId] {
p := &f.Heads[*r.IpscId][i]
if *p.Id == *r.TunnelId {
p.OracleCanInitiate = core.IpSecConnectionTunnelOracleCanInitiateEnum(r.OracleInitiation)
}
}
return core.UpdateIPSecConnectionTunnelResponse{}, nil
}
func (f *networkFake) GetIPSecConnectionTunnelSharedSecret(context.Context, core.GetIPSecConnectionTunnelSharedSecretRequest) (core.GetIPSecConnectionTunnelSharedSecretResponse, error) {
return core.GetIPSecConnectionTunnelSharedSecretResponse{IpSecConnectionTunnelSharedSecret: core.IpSecConnectionTunnelSharedSecret{SharedSecret: common.String("synthetic-secret")}}, nil
}
func TestWANReplacement(t *testing.T) {
f := &networkFake{Heads: map[string][]core.IpSecConnectionTunnel{}, Cpes: []core.Cpe{{Id: common.String("foreign"), IpAddress: common.String("8.8.4.4"), FreeformTags: map[string]string{"owner": "relay"}}}}
c := Client{API: f, Config: config.Cloud{Compartment: "synthetic-compartment", DRG: "synthetic-drg", Address: "10.76.79.10"}}
ctx := context.Background()
first, err := c.Reconcile(ctx, "8.8.4.4")
if err != nil {
t.Fatal(err)
}
if !first.Ready || len(first.Peers) != 2 || f.Creates != 2 {
t.Fatal("initial headend publication failed")
}
for _, head := range f.Heads[*f.Connections[0].Id] {
if head.OracleCanInitiate != core.IpSecConnectionTunnelOracleCanInitiateInitiatorOrResponder {
t.Fatal("OCI initiation disabled")
}
}
if _, err = c.Reconcile(ctx, "8.8.4.4"); err != nil {
t.Fatal(err)
}
if f.Creates != 2 || f.Deletes != 0 {
t.Fatal("steady reconciliation mutated cloud resources")
}
next, err := c.Reconcile(ctx, "8.8.8.8")
if err != nil {
t.Fatal(err)
}
if next.Bundle.WAN != "8.8.8.8" || len(next.Peers) != 4 || f.Creates != 4 || f.Deletes != 0 {
t.Fatal("replacement did not retain prior peers until new tunnels established")
}
current := f.Connections[1]
if *current.CpeLocalIdentifier != "8.8.8.8" || len(current.StaticRoutes) != 1 || current.StaticRoutes[0] != "10.76.79.10/32" {
t.Fatal("replacement retained old WAN identity or node routing")
}
heads := f.Heads[*current.Id]
for i := range heads {
heads[i].Status = core.IpSecConnectionTunnelStatusUp
}
f.Heads[*current.Id] = heads
if _, err = c.Reconcile(ctx, "8.8.8.8"); err != nil {
t.Fatal(err)
}
if f.Deletes != 1 {
t.Fatal("old connection not retired after both new headends became healthy")
}
if _, err = c.Reconcile(ctx, "8.8.8.8"); err != nil {
t.Fatal(err)
}
if f.Deletes != 2 || len(f.Cpes) != 2 || *f.Cpes[0].Id != "foreign" {
t.Fatal("CPE retirement affected another ingress owner")
}
steady, err := c.Reconcile(ctx, "8.8.8.8")
if err != nil {
t.Fatal(err)
}
if len(steady.Peers) != 2 || f.Creates != 4 || f.Deletes != 2 {
t.Fatal("obsolete peer allowlist survived replacement")
}
}
func TestInitiationDrift(t *testing.T) {
f := &networkFake{Heads: map[string][]core.IpSecConnectionTunnel{}}
c := Client{API: f, Config: config.Cloud{Compartment: "synthetic", DRG: "synthetic", Address: "10.76.79.10"}}
ctx := context.Background()
if _, err := c.Reconcile(ctx, "8.8.4.4"); err != nil {
t.Fatal(err)
}
id := *f.Connections[0].Id
f.Heads[id][0].OracleCanInitiate = core.IpSecConnectionTunnelOracleCanInitiateResponderOnly
if _, err := c.Reconcile(ctx, "8.8.4.4"); err != nil {
t.Fatal(err)
}
if f.Updates != 1 {
t.Fatal("initiation drift not reconciled")
}
if _, err := c.Reconcile(ctx, "8.8.4.4"); err != nil {
t.Fatal(err)
}
if f.Updates != 1 {
t.Fatal("unchanged initiation rewritten")
}
}
type apiError struct {
Status int
Code string
}
func (e apiError) Error() string { return e.Code }
func (e apiError) GetHTTPStatusCode() int { return e.Status }
func (e apiError) GetCode() string { return e.Code }
func (e apiError) GetMessage() string { return e.Code }
func (e apiError) GetOpcRequestID() string {
return "synthetic-request"
}
type retryFake struct {
*networkFake
CpeTokens map[string]string
IPSecTokens map[string]string
LoseKind string
LostToken string
HiddenCpe string
HiddenIPSec string
Replayed []string
Invalid int
}
func retryNetwork() *retryFake {
return &retryFake{
networkFake: &networkFake{Heads: map[string][]core.IpSecConnectionTunnel{}},
CpeTokens: map[string]string{}, IPSecTokens: map[string]string{},
}
}
func (f *retryFake) ListCpes(context.Context, core.ListCpesRequest) (core.ListCpesResponse, error) {
var result core.ListCpesResponse
for _, p := range f.Cpes {
if *p.Id != f.HiddenCpe {
result.Items = append(result.Items, p)
}
}
return result, nil
}
func (f *retryFake) ListIPSecConnections(context.Context, core.ListIPSecConnectionsRequest) (core.ListIPSecConnectionsResponse, error) {
var result core.ListIPSecConnectionsResponse
for _, p := range f.Connections {
if *p.Id != f.HiddenIPSec {
result.Items = append(result.Items, p)
}
}
return result, nil
}
func (f *retryFake) CreateCpe(ctx context.Context, r core.CreateCpeRequest) (core.CreateCpeResponse, error) {
if id, ok := f.CpeTokens[*r.OpcRetryToken]; ok {
for _, p := range f.Cpes {
if *p.Id == id {
f.Replayed = append(f.Replayed, *r.OpcRetryToken)
return core.CreateCpeResponse{Cpe: p}, nil
}
}
f.Invalid++
return core.CreateCpeResponse{}, apiError{409, "InvalidatedRetryToken"}
}
result, err := f.networkFake.CreateCpe(ctx, r)
if err != nil {
return result, err
}
f.CpeTokens[*r.OpcRetryToken] = *result.Id
if f.LoseKind == "cpe" {
f.LoseKind, f.LostToken, f.HiddenCpe = "", *r.OpcRetryToken, *result.Id
return core.CreateCpeResponse{}, context.DeadlineExceeded
}
return result, nil
}
func (f *retryFake) CreateIPSecConnection(ctx context.Context, r core.CreateIPSecConnectionRequest) (core.CreateIPSecConnectionResponse, error) {
if id, ok := f.IPSecTokens[*r.OpcRetryToken]; ok {
for _, p := range f.Connections {
if *p.Id == id {
f.Replayed = append(f.Replayed, *r.OpcRetryToken)
return core.CreateIPSecConnectionResponse{IpSecConnection: p}, nil
}
}
f.Invalid++
return core.CreateIPSecConnectionResponse{}, apiError{409, "InvalidatedRetryToken"}
}
result, err := f.networkFake.CreateIPSecConnection(ctx, r)
if err != nil {
return result, err
}
f.IPSecTokens[*r.OpcRetryToken] = *result.Id
if f.LoseKind == "ipsec" {
f.LoseKind, f.LostToken, f.HiddenIPSec = "", *r.OpcRetryToken, *result.Id
return core.CreateIPSecConnectionResponse{}, context.DeadlineExceeded
}
return result, nil
}
func settleWAN(t *testing.T, c *Client, f *retryFake, wan string) {
t.Helper()
if state, err := c.Reconcile(context.Background(), wan); err != nil || !state.Ready {
t.Fatalf("WAN %s: ready=%v error=%v", wan, state.Ready, err)
}
for _, heads := range f.Heads {
for i := range heads {
heads[i].Status = core.IpSecConnectionTunnelStatusUp
}
}
for range 2 {
if _, err := c.Reconcile(context.Background(), wan); err != nil {
t.Fatal(err)
}
}
}
func TestWANReturn(t *testing.T) {
f := retryNetwork()
c := Client{API: f, Config: config.Cloud{Compartment: "synthetic", DRG: "synthetic", Address: "192.0.2.10"}}
for _, wan := range []string{"198.51.100.10", "198.51.100.20", "198.51.100.10"} {
settleWAN(t, &c, f, wan)
}
if f.Creates != 6 || f.Deletes != 4 || f.Invalid != 2 || len(f.Cpes) != 1 || len(f.Connections) != 1 {
t.Fatalf("WAN return failed to retire and replace: creates=%d deletes=%d invalid=%d CPEs=%d connections=%d", f.Creates, f.Deletes, f.Invalid, len(f.Cpes), len(f.Connections))
}
if len(f.CpeTokens) != 3 || len(f.IPSecTokens) != 3 {
t.Fatal("distinct resource generations reused a creation token")
}
}
func TestCreateTimeout(t *testing.T) {
for _, kind := range []string{"cpe", "ipsec"} {
t.Run(kind, func(t *testing.T) {
f := retryNetwork()
cfg := config.Cloud{Compartment: "synthetic", DRG: "synthetic", Address: "192.0.2.10"}
c := Client{API: f, Config: cfg}
settleWAN(t, &c, f, "198.51.100.10")
settleWAN(t, &c, f, "198.51.100.20")
f.LoseKind = kind
if _, err := c.Reconcile(context.Background(), "198.51.100.10"); !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("create did not commit with a lost reply: %v", err)
}
replacement := Client{API: f, Config: cfg}
state, err := replacement.Reconcile(context.Background(), "198.51.100.10")
if err != nil || !state.Ready {
t.Fatalf("replacement leader failed: ready=%v error=%v", state.Ready, err)
}
if !slices.Contains(f.Replayed, f.LostToken) || f.Creates != 6 || len(f.Cpes) != 2 || len(f.Connections) != 2 {
t.Fatalf("ambiguous create was not replayed: creates=%d CPEs=%d connections=%d", f.Creates, len(f.Cpes), len(f.Connections))
}
f.HiddenCpe, f.HiddenIPSec = "", ""
if _, err := replacement.Reconcile(context.Background(), "198.51.100.10"); err != nil {
t.Fatal(err)
}
if f.Creates != 6 {
t.Fatal("visibility restoration created another resource")
}
})
}
}
func TestRetryErrors(t *testing.T) {
for _, want := range []error{context.DeadlineExceeded, apiError{409, "IncorrectState"}, apiError{403, "InvalidatedRetryToken"}} {
calls := 0
_, got := create(common.String("synthetic-token"), func(*string) (int, error) {
calls++
return 0, want
})
if calls != 1 || !errors.Is(got, want) {
t.Fatalf("unrelated error was retried: %v; calls=%d", got, calls)
}
}
}
func TestRouteDrift(t *testing.T) {
f := &networkFake{Heads: map[string][]core.IpSecConnectionTunnel{}, HoldRoutes: true}
c := Client{API: f, Config: config.Cloud{Compartment: "synthetic", DRG: "synthetic", Address: "192.0.2.10"}}
ctx := context.Background()
if _, err := c.Reconcile(ctx, "198.51.100.10"); err != nil {
t.Fatal(err)
}
c.Config.Address = "192.0.2.11"
for range 2 {
state, err := c.Reconcile(ctx, "198.51.100.10")
if err != nil || state.Ready || len(state.Peers) != 2 {
t.Fatalf("pending route exposed runtime or removed peers: ready=%v peers=%d error=%v", state.Ready, len(state.Peers), err)
}
}
if f.RouteUpdates != 2 || !slices.Equal(f.PendingRoutes, []string{"192.0.2.11/32"}) || f.Connections[0].StaticRoutes[0] != "192.0.2.10/32" {
t.Fatal("route update did not target the configured address")
}
f.Connections[0].StaticRoutes = slices.Clone(f.PendingRoutes)
for range 2 {
state, err := c.Reconcile(ctx, "198.51.100.10")
if err != nil || !state.Ready {
t.Fatalf("observed route was not published: ready=%v error=%v", state.Ready, err)
}
}
if f.Creates != 2 || f.Deletes != 0 || f.RouteUpdates != 2 {
t.Fatal("route reconciliation replaced resources or repeated a completed update")
}
}

View file

@ -0,0 +1,67 @@
package main
import (
"context"
"fmt"
"io"
"log/slog"
"os"
"os/signal"
"syscall"
"quorate.dev/services/short/controller"
"quorate.dev/services/short/tunnel"
"quorate.dev/services/short/vault"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT)
defer stop()
if err := run(ctx, os.Args[1:]); err != nil {
slog.Error("short ingress stopped", "error", err)
os.Exit(1)
}
}
func run(ctx context.Context, args []string) error {
if len(args) == 0 {
return fmt.Errorf("expected controller, tunnel, seed, or stage")
}
switch args[0] {
case "controller":
return controller.Run(ctx, "/config", os.Getenv("POD_NAMESPACE"), os.Getenv("POD_UID"), os.Getenv("VAULT_ADDR"), os.Getenv("OPNSENSE_URL"))
case "tunnel":
return tunnel.Run(ctx, "/config/runtime.json", os.Getenv("POD_NAMESPACE"), os.Getenv("POD_NAME"))
case "seed":
kv, err := vault.Login(ctx, os.Getenv("VAULT_ADDR"), "short-seed", "/var/run/secrets/kubernetes.io/serviceaccount/token")
if err != nil {
return err
}
return kv.Seed(ctx, "short/api")
case "stage":
if len(args) != 2 {
return fmt.Errorf("expected binary destination")
}
file, err := os.Executable()
if err != nil {
return err
}
source, err := os.Open(file)
if err != nil {
return err
}
defer source.Close()
target, err := os.OpenFile(args[1], os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0755)
if err != nil {
return err
}
_, copyErr := io.Copy(target, source)
closeErr := target.Close()
if copyErr != nil {
return copyErr
}
return closeErr
default:
return fmt.Errorf("unknown command")
}
}

View file

@ -0,0 +1,103 @@
package config
import (
"encoding/json"
"fmt"
"net/netip"
"os"
)
type Peer struct {
ID string `json:"id"`
Address string `json:"address"`
Secret string `json:"secret"`
}
type Bundle struct {
WAN string `json:"wan"`
Peers []Peer `json:"peers"`
}
type Runtime struct {
Bundle
Path Path `json:"path"`
}
type Cloud struct {
Tenancy string `json:"tenancy"`
User string `json:"user"`
Fingerprint string `json:"fingerprint"`
Region string `json:"region"`
Compartment string `json:"compartment"`
DRG string `json:"drg"`
Address string `json:"address"`
Network string `json:"network"`
}
type Path struct {
Address string `json:"address"`
Network string `json:"network"`
Gateway string `json:"gateway"`
Ports []uint16 `json:"ports"`
}
func Read[T any](path string) (T, error) {
var value T
data, err := os.ReadFile(path)
if err != nil {
return value, err
}
err = json.Unmarshal(data, &value)
return value, err
}
func (b Bundle) Validate() error {
if err := IPv4(b.WAN); err != nil {
return fmt.Errorf("WAN: %w", err)
}
if len(b.Peers) != 2 {
return fmt.Errorf("expected two OCI headends")
}
for _, p := range b.Peers {
if p.ID == "" || p.Secret == "" {
return fmt.Errorf("incomplete headend")
}
if err := IPv4(p.Address); err != nil {
return fmt.Errorf("headend: %w", err)
}
}
if b.Peers[0].ID == b.Peers[1].ID || b.Peers[0].Address == b.Peers[1].Address {
return fmt.Errorf("duplicate headend")
}
return nil
}
func (p Path) Validate() error {
if err := IPv4(p.Address); err != nil {
return err
}
if err := IPv4(p.Gateway); err != nil {
return fmt.Errorf("Gateway: %w", err)
}
n, err := netip.ParsePrefix(p.Network)
if err != nil || !n.Addr().Is4() {
return fmt.Errorf("invalid OCI network")
}
if len(p.Ports) == 0 {
return fmt.Errorf("empty service allowlist")
}
for _, port := range p.Ports {
if port == 0 {
return fmt.Errorf("invalid service port")
}
}
return nil
}
func IPv4(value string) error {
ip, err := netip.ParseAddr(value)
if err != nil || !ip.Is4() || !ip.IsGlobalUnicast() {
return fmt.Errorf("invalid unicast IPv4")
}
return nil
}

View file

@ -0,0 +1,177 @@
package controller
import (
"context"
"encoding/json"
"log/slog"
"os"
"reflect"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/leaderelection"
"k8s.io/client-go/tools/leaderelection/resourcelock"
"quorate.dev/services/short/cloud"
"quorate.dev/services/short/config"
"quorate.dev/services/short/vault"
"quorate.dev/services/short/wan"
)
var group = schema.GroupVersionResource{Group: "cilium.io", Version: "v2", Resource: "ciliumcidrgroups"}
type Runner struct {
Cloud *cloud.Client
WAN *wan.Client
Kube kubernetes.Interface
Dynamic dynamic.Interface
VaultURL string
}
func Run(ctx context.Context, directory, namespace, identity, vaultURL, opnURL string) error {
c, err := config.Read[config.Cloud](directory + "/cloud.json")
if err != nil {
return err
}
key, err := os.ReadFile(directory + "/private_key")
if err != nil {
return err
}
oci, err := cloud.New(c, string(key))
if err != nil {
return err
}
opn, err := wan.New(opnURL, directory+"/opnsense")
if err != nil {
return err
}
kc, err := rest.InClusterConfig()
if err != nil {
return err
}
kube, err := kubernetes.NewForConfig(kc)
if err != nil {
return err
}
dyn, err := dynamic.NewForConfig(kc)
if err != nil {
return err
}
r := Runner{Cloud: oci, WAN: opn, Kube: kube, Dynamic: dyn, VaultURL: vaultURL}
lock := &resourcelock.LeaseLock{LeaseMeta: metav1.ObjectMeta{Name: "controller", Namespace: namespace}, Client: kube.CoordinationV1(), LockConfig: resourcelock.ResourceLockConfig{Identity: identity}}
elector, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
Lock: lock, LeaseDuration: 30 * time.Second, RenewDeadline: 20 * time.Second, RetryPeriod: 5 * time.Second,
ReleaseOnCancel: true, Callbacks: leaderelection.LeaderCallbacks{
OnStartedLeading: r.loop,
OnStoppedLeading: func() { slog.Info("controller ownership ended") },
},
})
if err != nil {
return err
}
elector.Run(ctx)
return nil
}
func (r *Runner) loop(ctx context.Context) {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
var kv *vault.Client
var login time.Time
for {
if kv == nil || time.Since(login) >= 30*time.Minute {
client, err := vault.Login(ctx, r.VaultURL, "short-controller", "/var/run/secrets/kubernetes.io/serviceaccount/token")
if err != nil {
slog.Error("Vault login", "error", err)
} else {
kv = client
login = time.Now()
}
}
if kv != nil {
request, cancel := context.WithTimeout(ctx, 20*time.Second)
err := r.step(request, kv)
cancel()
if err != nil {
slog.Error("reconcile short ingress", "error", err)
}
}
select {
case <-ctx.Done():
return
case <-ticker.C:
}
}
}
func (r *Runner) step(ctx context.Context, kv *vault.Client) error {
wan, err := r.WAN.Address(ctx)
if err != nil {
return err
}
state, err := r.Cloud.Reconcile(ctx, wan)
if err != nil {
return err
}
resource := r.Dynamic.Resource(group)
current, err := resource.Get(ctx, "short", metav1.GetOptions{})
if err != nil {
return err
}
want := make([]any, len(state.Peers))
for i, value := range state.Peers {
want[i] = value
}
got, _, err := unstructured.NestedSlice(current.Object, "spec", "externalCIDRs")
if err != nil {
return err
}
if !reflect.DeepEqual(want, got) {
if err := unstructured.SetNestedSlice(current.Object, want, "spec", "externalCIDRs"); err != nil {
return err
}
if _, err = resource.Update(ctx, current, metav1.UpdateOptions{FieldManager: "short"}); err != nil {
return err
}
}
if !state.Ready {
slog.Info("waiting for OCI headends")
return nil
}
svc, err := r.Kube.CoreV1().Services("kube-system").Get(ctx, "cilium-gateway-gateway-short", metav1.GetOptions{})
if err != nil {
return err
}
ports := make([]uint16, 0, len(svc.Spec.Ports))
for _, p := range svc.Spec.Ports {
ports = append(ports, uint16(p.Port))
}
runtime := config.Runtime{Bundle: state.Bundle, Path: config.Path{Address: r.Cloud.Config.Address, Network: r.Cloud.Config.Network, Gateway: svc.Spec.ClusterIP, Ports: ports}}
if err = runtime.Bundle.Validate(); err != nil {
return err
}
if err = runtime.Path.Validate(); err != nil {
return err
}
data, err := json.Marshal(runtime)
if err != nil {
return err
}
record, err := kv.Read(ctx, "short/peers")
if err != nil {
return err
}
if record.Data["config"] == string(data) {
return nil
}
record.Data = map[string]any{"config": string(data)}
if err = kv.Write(ctx, "short/peers", record); err != nil {
return err
}
slog.Info("published short tunnel configuration", "wan", wan, "headends", len(state.Bundle.Peers), "ports", ports)
return nil
}

View file

@ -0,0 +1,177 @@
package controller
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"sync"
"sync/atomic"
"testing"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/core"
kcore "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
dynfake "k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/kubernetes/fake"
"quorate.dev/services/short/cloud"
"quorate.dev/services/short/config"
"quorate.dev/services/short/vault"
"quorate.dev/services/short/wan"
)
type publishedCloud struct {
cloud.Network
phase atomic.Int32
}
func (f *publishedCloud) generations() []int {
switch f.phase.Load() {
case 0:
return []int{0}
case 1:
return []int{0, 1}
default:
return []int{1}
}
}
func (f *publishedCloud) ListCpes(context.Context, core.ListCpesRequest) (core.ListCpesResponse, error) {
var result core.ListCpesResponse
for _, i := range f.generations() {
result.Items = append(result.Items, core.Cpe{Id: common.String(fmt.Sprint(i)), IpAddress: common.String(fmt.Sprintf("192.0.2.%d", i+2)), FreeformTags: map[string]string{"owner": "short"}})
}
return result, nil
}
func (f *publishedCloud) ListIPSecConnections(context.Context, core.ListIPSecConnectionsRequest) (core.ListIPSecConnectionsResponse, error) {
var result core.ListIPSecConnectionsResponse
for _, i := range f.generations() {
result.Items = append(result.Items, core.IpSecConnection{Id: common.String(fmt.Sprint(i)), CpeId: common.String(fmt.Sprint(i)), LifecycleState: core.IpSecConnectionLifecycleStateAvailable, StaticRoutes: []string{"198.18.0.10/32"}, FreeformTags: map[string]string{"owner": "short"}})
}
return result, nil
}
func (f *publishedCloud) ListIPSecConnectionTunnels(_ context.Context, r core.ListIPSecConnectionTunnelsRequest) (core.ListIPSecConnectionTunnelsResponse, error) {
var result core.ListIPSecConnectionTunnelsResponse
for i := 1; i <= 2; i++ {
result.Items = append(result.Items, core.IpSecConnectionTunnel{
Id: common.String(fmt.Sprintf("%s-%d", *r.IpscId, i)), VpnIp: common.String(fmt.Sprintf("192.0.2.%s%d", map[string]string{"0": "1", "1": "2"}[*r.IpscId], i)),
LifecycleState: core.IpSecConnectionTunnelLifecycleStateAvailable, Status: core.IpSecConnectionTunnelStatusDown,
OracleCanInitiate: core.IpSecConnectionTunnelOracleCanInitiateInitiatorOrResponder,
})
}
return result, nil
}
func (f *publishedCloud) GetIPSecConnectionTunnelSharedSecret(_ context.Context, r core.GetIPSecConnectionTunnelSharedSecretRequest) (core.GetIPSecConnectionTunnelSharedSecretResponse, error) {
return core.GetIPSecConnectionTunnelSharedSecretResponse{IpSecConnectionTunnelSharedSecret: core.IpSecConnectionTunnelSharedSecret{SharedSecret: common.String("synthetic-" + *r.TunnelId)}}, nil
}
func TestWANPublication(t *testing.T) {
ctx := context.Background()
api := &publishedCloud{}
dyn := dynfake.NewSimpleDynamicClient(runtime.NewScheme(), &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "cilium.io/v2", "kind": "CiliumCIDRGroup", "metadata": map[string]any{"name": "short"}, "spec": map[string]any{"externalCIDRs": []any{}},
}})
kube := fake.NewClientset(&kcore.Service{ObjectMeta: metav1.ObjectMeta{Name: "cilium-gateway-gateway-short", Namespace: "kube-system"}, Spec: kcore.ServiceSpec{ClusterIP: "192.0.2.100", Ports: []kcore.ServicePort{{Port: 8080}}}})
var mu sync.Mutex
version := 1
data := map[string]any{"config": "{}"}
writes := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
mu.Lock()
defer mu.Unlock()
w.Header().Set("Content-Type", "application/json")
switch req.URL.Path {
case "/api/interfaces/overview/interfaces_info":
if req.Method != http.MethodGet {
t.Error("WAN reader attempted a mutation")
}
address := "192.0.2.2"
if api.phase.Load() > 0 {
address = "192.0.2.3"
}
fmt.Fprintf(w, `{"rows":[{"identifier":"WAN","ipv4":[{"ipaddr":%q}]}]}`, address)
case "/v1/secret/data/short/peers":
if req.Method == http.MethodGet {
json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"data": data, "metadata": map[string]int{"version": version}}})
return
}
var input struct {
Options struct{ CAS int } `json:"options"`
Data map[string]any `json:"data"`
}
if err := json.NewDecoder(req.Body).Decode(&input); err != nil {
t.Error(err)
}
if req.Method != http.MethodPost || input.Options.CAS != version {
t.Error("configuration publication lost its CAS contract")
http.Error(w, "conflict", http.StatusBadRequest)
return
}
var published config.Runtime
if err := json.Unmarshal([]byte(input.Data["config"].(string)), &published); err != nil {
t.Error(err)
}
current, _ := dyn.Resource(group).Get(ctx, "short", metav1.GetOptions{})
allowed, _, _ := unstructured.NestedStringSlice(current.Object, "spec", "externalCIDRs")
for _, p := range published.Peers {
found := false
for _, cidr := range allowed {
found = found || cidr == p.Address+"/32"
}
if !found {
t.Error("published a peer before admitting its address")
}
}
data = input.Data
version++
writes++
default:
t.Errorf("unexpected request: %s", req.URL.Path)
http.NotFound(w, req)
}
}))
defer server.Close()
r := Runner{Cloud: &cloud.Client{API: api, Config: config.Cloud{Address: "198.18.0.10", Network: "198.18.1.0/24"}}, WAN: &wan.Client{URL: server.URL, HTTP: server.Client()}, Kube: kube, Dynamic: dyn}
kv := &vault.Client{URL: server.URL, HTTP: server.Client(), Token: "synthetic-token"}
for phase, wantCIDRs := range []int{2, 4, 2} {
api.phase.Store(int32(phase))
if err := r.step(ctx, kv); err != nil {
t.Fatal(err)
}
mu.Lock()
value := data["config"].(string)
count := writes
mu.Unlock()
var published config.Runtime
if err := json.Unmarshal([]byte(value), &published); err != nil {
t.Fatal(err)
}
wantWAN, wantWrites := "192.0.2.2", 1
if phase > 0 {
wantWAN, wantWrites = "192.0.2.3", 2
}
if published.WAN != wantWAN || count != wantWrites || published.Path.Address != "198.18.0.10" || published.Path.Network != "198.18.1.0/24" || published.Path.Gateway != "192.0.2.100" || !reflect.DeepEqual(published.Path.Ports, []uint16{8080}) {
t.Fatalf("wrong runtime publication: %s; writes=%d", value, count)
}
current, _ := dyn.Resource(group).Get(ctx, "short", metav1.GetOptions{})
cidrs, _, _ := unstructured.NestedStringSlice(current.Object, "spec", "externalCIDRs")
if len(cidrs) != wantCIDRs {
t.Fatalf("wrong peer admission transition: %v", cidrs)
}
if directory := os.Getenv("SHORT_FIXTURE"); directory != "" && phase < 2 {
if err := os.WriteFile(filepath.Join(directory, fmt.Sprintf("runtime%d.json", phase)), []byte(value), 0600); err != nil {
t.Fatal(err)
}
}
}
}

76
services/short/go.mod Normal file
View file

@ -0,0 +1,76 @@
module quorate.dev/services/short
go 1.26.4
require (
github.com/google/cel-go v0.26.0
github.com/google/nftables v0.3.0
github.com/oracle/oci-go-sdk/v65 v65.121.0
github.com/strongswan/govici v0.7.0
github.com/vishvananda/netlink v1.3.1
golang.org/x/sys v0.47.0
k8s.io/api v0.36.2
k8s.io/apimachinery v0.36.2
k8s.io/client-go v0.36.2
sigs.k8s.io/yaml v1.6.0
)
require (
cel.dev/expr v0.24.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v1.0.0 // indirect
github.com/go-openapi/jsonreference v1.0.0 // indirect
github.com/go-openapi/swag v0.27.1 // indirect
github.com/go-openapi/swag/cmdutils v0.27.1 // indirect
github.com/go-openapi/swag/conv v0.27.1 // indirect
github.com/go-openapi/swag/fileutils v0.27.1 // indirect
github.com/go-openapi/swag/jsonutils v0.27.1 // indirect
github.com/go-openapi/swag/loading v0.27.1 // indirect
github.com/go-openapi/swag/mangling v0.27.1 // indirect
github.com/go-openapi/swag/netutils v0.27.1 // indirect
github.com/go-openapi/swag/pools v0.27.1 // indirect
github.com/go-openapi/swag/stringutils v0.27.1 // indirect
github.com/go-openapi/swag/typeutils v0.27.1 // indirect
github.com/go-openapi/swag/yamlutils v0.27.1 // indirect
github.com/gofrs/flock v0.10.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
github.com/mdlayher/socket v0.5.0 // indirect
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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/sony/gobreaker/v2 v2.4.0 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect
k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect
)

171
services/short/go.sum Normal file
View file

@ -0,0 +1,171 @@
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg=
github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE=
github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE=
github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM=
github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU=
github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs=
github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM=
github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU=
github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY=
github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE=
github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA=
github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w=
github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU=
github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ=
github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E=
github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8=
github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc=
github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA=
github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
github.com/gofrs/flock v0.10.0 h1:SHMXenfaB03KbroETaCMtbBg3Yn29v4w1r+tgy4ff4k=
github.com/gofrs/flock v0.10.0/go.mod h1:FirDy1Ing0mI2+kB6wk+vyyAH+e6xiE+EYA0jnzV9jc=
github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg=
github.com/google/nftables v0.3.0/go.mod h1:BCp9FsrbF1Fn/Yu6CLUc9GGZFw/+hsxfluNXXmxBfRM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
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/oracle/oci-go-sdk/v65 v65.121.0 h1:1J+5ARgrodrx8kzFy/hxznaoUzz43jr0EestCzEaOHw=
github.com/oracle/oci-go-sdk/v65 v65.121.0/go.mod h1:Pzy+BpgkDesvGZXEHgslwhIYobHCPHg6wRta1mWnlqQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sony/gobreaker/v2 v2.4.0 h1:g2KJRW1Ubty3+ZOcSEUN7K+REQJdN6yo6XvaML+jptg=
github.com/sony/gobreaker/v2 v2.4.0/go.mod h1:pTyFJgcZ3h2tdQVLZZruK2C0eoFL1fb/G83wK1ZQl+s=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/strongswan/govici v0.7.0 h1:m0BaL5hY+2khnQZYDaKJkd4Ji1jFrNzS0fg+GQS7QnE=
github.com/strongswan/govici v0.7.0/go.mod h1:WvC3Lo9kEzjxUb5xNe2B4NczQpa7+cZMIy2x8eZLzSE=
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
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=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw=
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY=
k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg=
k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ=
k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4=
k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI=
k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A=
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I=
k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs=
k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
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/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q=
sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View file

@ -0,0 +1,14 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: short-vso
namespace: short
automountServiceAccountToken: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: short-seed
namespace: short
imagePullSecrets:
- name: pull

View file

@ -0,0 +1,55 @@
apiVersion: batch/v1
kind: Job
metadata:
name: seed
namespace: short
annotations:
kustomize.toolkit.fluxcd.io/force: Enabled
spec:
completions: 2
parallelism: 2
template:
metadata:
labels:
app.kubernetes.io/name: seed
app.kubernetes.io/instance: short
spec:
serviceAccountName: short-seed
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: seed
image: short-image
args:
- seed
env:
- name: VAULT_ADDR
value: https://vault.vault.svc.cluster.local:8200
- name: SSL_CERT_FILE
value: /ca/ca.crt
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
memory: 128Mi
volumeMounts:
- name: ca
mountPath: /ca
readOnly: true
volumes:
- name: ca
secret:
secretName: ca

View file

@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- accounts.yaml
- vault.yaml
- job.yaml
- policy.yaml
- vault-access.yaml

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: Namespace
metadata:
name: short
labels: {gateway.quorate.internal/allow-short: "true"}

View file

@ -0,0 +1,31 @@
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: seed
namespace: short
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: seed
egress:
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s:k8s-app: kube-dns
toPorts:
- ports:
- port: '53'
protocol: UDP
- port: '53'
protocol: TCP
rules:
dns:
- matchPattern: '*'
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: vault
app.kubernetes.io/name: vault
toPorts:
- ports:
- port: '8200'
protocol: TCP

View file

@ -0,0 +1,17 @@
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata: {name: short, namespace: vault}
spec:
endpointSelector:
matchLabels: {app.kubernetes.io/name: vault, component: server}
ingress:
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: short
app.kubernetes.io/instance: short
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values: [seed, controller]
toPorts:
- ports: [{port: "8200", protocol: TCP}]

View file

@ -0,0 +1,60 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: short
namespace: short
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: short-vso
serviceAccount: short-vso
vaultConnectionRef: vault/vault
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: pull
namespace: short
spec:
vaultAuthRef: short
type: kv-v2
mount: secret
path: quay/robots/cluster_pull
refreshAfter: 1h
destination:
create: true
overwrite: true
name: pull
type: kubernetes.io/dockerconfigjson
transformation:
excludeRaw: true
excludes:
- .*
templates:
.dockerconfigjson:
text: '{"auths":{"registry.quorate.internal":{"auth":"{{ printf "%s:%s"
(get .Secrets "username") (get .Secrets "token") | b64enc }}"}}}'
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
metadata:
name: ca
namespace: short
spec:
vaultAuthRef: short
mount: pki
path: cert/ca
requestHTTPMethod: GET
refreshAfter: 1h
destination:
create: true
overwrite: true
name: ca
transformation:
excludeRaw: true
excludes:
- .*
templates:
ca.crt:
text: '{{ get .Secrets "certificate" }}'

View file

@ -0,0 +1,114 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller
namespace: short
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: controller
template:
metadata:
labels:
app.kubernetes.io/name: controller
app.kubernetes.io/instance: short
spec:
serviceAccountName: short-controller
imagePullSecrets:
- name: pull
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app.kubernetes.io/name: controller
app.kubernetes.io/instance: short
containers:
- name: controller
image: short-image
args:
- controller
env:
- name: POD_UID
valueFrom:
fieldRef:
fieldPath: metadata.uid
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: VAULT_ADDR
value: https://vault.vault.svc.cluster.local:8200
- name: OPNSENSE_URL
value: https://10.212.111.120:8443
- name: SSL_CERT_FILE
value: /ca/ca.crt
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 192Mi
volumeMounts:
- name: config
mountPath: /config
readOnly: true
- name: ca
mountPath: /ca
readOnly: true
volumes:
- name: ca
secret:
secretName: ca
- name: config
projected:
defaultMode: 288
sources:
- secret:
name: api
items:
- key: private_key
path: private_key
- secret:
name: cloud
items:
- key: config
path: cloud.json
- secret:
name: opnsense
items:
- key: key
path: opnsense/key
- key: secret
path: opnsense/secret
- secret:
name: ca-opnsense
items:
- key: ca.crt
path: opnsense/ca.crt
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: controller
namespace: short
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: controller

View file

@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- rbac.yaml
- vault.yaml
- policy.yaml

View file

@ -0,0 +1,43 @@
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: controller
namespace: short
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: controller
egress:
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s:k8s-app: kube-dns
toPorts:
- ports:
- port: '53'
protocol: UDP
- port: '53'
protocol: TCP
rules:
dns:
- matchPattern: '*'
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: vault
app.kubernetes.io/name: vault
toPorts:
- ports:
- port: '8200'
protocol: TCP
- toCIDR:
- 10.212.111.120/32
toPorts:
- ports:
- port: '8443'
protocol: TCP
- toFQDNs:
- matchPattern: iaas.*.oraclecloud.com
toPorts:
- ports:
- port: '443'
protocol: TCP

View file

@ -0,0 +1,107 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: short-controller
namespace: short
---
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
name: controller
namespace: short
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: controller
namespace: short
rules:
- apiGroups:
- coordination.k8s.io
resources:
- leases
resourceNames:
- controller
verbs:
- get
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: controller
namespace: short
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: controller
subjects:
- kind: ServiceAccount
name: short-controller
namespace: short
---
apiVersion: cilium.io/v2
kind: CiliumCIDRGroup
metadata:
name: short
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
spec:
externalCIDRs: []
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: short-peers
rules:
- apiGroups:
- cilium.io
resources:
- ciliumcidrgroups
resourceNames:
- short
verbs:
- get
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: short-peers
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: short-peers
subjects:
- kind: ServiceAccount
name: short-controller
namespace: short
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: short-gateway
namespace: kube-system
rules:
- apiGroups:
- ''
resources:
- services
resourceNames:
- cilium-gateway-gateway-short
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: short-gateway
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: short-gateway
subjects:
- kind: ServiceAccount
name: short-controller
namespace: short

View file

@ -0,0 +1,111 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: api
namespace: short
spec:
vaultAuthRef: short
type: kv-v2
mount: secret
path: short/api
refreshAfter: 1m
destination:
create: true
overwrite: true
name: api
transformation:
excludeRaw: true
excludes:
- .*
templates:
private_key:
text: '{{ get .Secrets "private_key" }}'
rolloutRestartTargets:
- kind: Deployment
name: controller
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: cloud
namespace: short
spec:
vaultAuthRef: short
type: kv-v2
mount: secret
path: short/cloud
refreshAfter: 1m
destination:
create: true
overwrite: true
name: cloud
transformation:
excludeRaw: true
excludes:
- .*
templates:
config:
text: '{{ get .Secrets "config" }}'
rolloutRestartTargets:
- kind: Deployment
name: controller
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: opnsense
namespace: short
spec:
vaultAuthRef: short
type: kv-v2
mount: secret
path: opnsense/interface-watcher
refreshAfter: 1h
destination:
create: true
overwrite: true
name: opnsense
transformation:
excludeRaw: true
excludes:
- .*
templates:
key:
text: '{{ get .Secrets "key" }}'
secret:
text: '{{ get .Secrets "secret" }}'
rolloutRestartTargets:
- kind: Deployment
name: controller
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: opnsense-ca
namespace: short
spec:
vaultAuthRef: short
type: kv-v2
mount: secret
path: ca/opnsense
hmacSecretData: true
refreshAfter: 1h
destination:
create: true
overwrite: true
name: ca-opnsense
transformation:
excludeRaw: true
excludes:
- .*
templates:
ca.crt:
text: >-
{{- $ca := get .Secrets "certificate" -}}
{{- if not (hasPrefix "-----BEGIN CERTIFICATE-----" $ca) -}}
{{- fail "CA publication is pending" -}}
{{- end -}}
{{- $ca -}}
rolloutRestartTargets:
- kind: Deployment
name: controller

Some files were not shown because too many files have changed in this diff Show more