ce7cb702218693d08ea6d83464d2457c9250027c

This commit is contained in:
ms2k 2026-11-12 20:46:28 +09:00
commit 34c5f8963c

View file

@ -0,0 +1,118 @@
package main
import (
"context"
"fmt"
"net"
"time"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/core"
)
type networkSecurityGroupClient interface {
UpdateNetworkSecurityGroupSecurityRules(context.Context, core.UpdateNetworkSecurityGroupSecurityRulesRequest) (core.UpdateNetworkSecurityGroupSecurityRulesResponse, error)
}
type ociReconciler struct {
client networkSecurityGroupClient
networkSecurityGroup string
securityRule string
wireguardDestinationPort int
wireguardURL string
recoveryTimeout time.Duration
probeWireGuard func(context.Context, string, time.Duration) error
}
func newOCIReconciler(cfg config) (*ociReconciler, error) {
provider := common.NewRawConfigurationProvider(
cfg.ociTenancy,
cfg.ociUser,
cfg.ociRegion,
cfg.ociFingerprint,
cfg.ociPrivateKey,
nil,
)
client, err := core.NewVirtualNetworkClientWithConfigurationProvider(provider)
if err != nil {
return nil, fmt.Errorf("create OCI virtual network client: %w", err)
}
return &ociReconciler{
client: &client,
networkSecurityGroup: cfg.networkSecurityGroup,
securityRule: cfg.securityRule,
wireguardDestinationPort: cfg.wireguardPort,
wireguardURL: cfg.wireguardURL,
recoveryTimeout: cfg.recoveryTimeout,
probeWireGuard: waitForWireGuard,
}, nil
}
func (r *ociReconciler) Reconcile(ctx context.Context, source net.IP) error {
if !validPublicIPv4(source) {
return fmt.Errorf("refuse invalid WAN source %q", source)
}
port := r.wireguardDestinationPort
protocol := "17"
description := "Home WireGuard peer"
sourceCIDR := source.String() + "/32"
response, err := r.client.UpdateNetworkSecurityGroupSecurityRules(ctx, core.UpdateNetworkSecurityGroupSecurityRulesRequest{
NetworkSecurityGroupId: common.String(r.networkSecurityGroup),
UpdateNetworkSecurityGroupSecurityRulesDetails: core.UpdateNetworkSecurityGroupSecurityRulesDetails{
SecurityRules: []core.UpdateSecurityRuleDetails{{
Direction: core.UpdateSecurityRuleDetailsDirectionIngress,
Id: common.String(r.securityRule),
Protocol: common.String(protocol),
Description: common.String(description),
Source: common.String(sourceCIDR),
SourceType: core.UpdateSecurityRuleDetailsSourceTypeCidrBlock,
IsStateless: common.Bool(false),
UdpOptions: &core.UdpOptions{DestinationPortRange: &core.PortRange{Min: common.Int(port), Max: common.Int(port)}},
}},
},
})
if err != nil {
return fmt.Errorf("update WireGuard NSG source: %w", err)
}
if err := verifyUpdatedRule(response.SecurityRules, r.securityRule, sourceCIDR, port); err != nil {
return err
}
if err := r.probeWireGuard(ctx, r.wireguardURL, r.recoveryTimeout); err != nil {
return fmt.Errorf("verify WireGuard connectivity: %w", err)
}
return nil
}
func verifyUpdatedRule(rules []core.SecurityRule, id, source string, port int) error {
if len(rules) != 1 {
return fmt.Errorf("OCI returned %d updated rules, want 1", len(rules))
}
rule := rules[0]
if stringValue(rule.Id) != id || stringValue(rule.Source) != source ||
stringValue(rule.Protocol) != "17" || rule.Direction != core.SecurityRuleDirectionIngress ||
rule.SourceType != core.SecurityRuleSourceTypeCidrBlock || boolValue(rule.IsStateless) ||
rule.UdpOptions == nil || rule.UdpOptions.DestinationPortRange == nil ||
intValue(rule.UdpOptions.DestinationPortRange.Min) != port ||
intValue(rule.UdpOptions.DestinationPortRange.Max) != port {
return fmt.Errorf("OCI returned an unexpected WireGuard rule")
}
return nil
}
func stringValue(value *string) string {
if value == nil {
return ""
}
return *value
}
func boolValue(value *bool) bool {
return value != nil && *value
}
func intValue(value *int) int {
if value == nil {
return 0
}
return *value
}