ce7cb702218693d08ea6d83464d2457c9250027c
This commit is contained in:
commit
34c5f8963c
1 changed files with 145336 additions and 0 deletions
256
services/short/cloud/oci.go
Normal file
256
services/short/cloud/oci.go
Normal 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)},
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue