blob: 51d813acc406658c3d9ca5ed015f640a47de7a79 [file] [log] [blame]
package curator
import (
"context"
"net/netip"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"source.monogon.dev/metropolis/node/core/identity"
"source.monogon.dev/metropolis/node/core/rpc"
ipb "source.monogon.dev/metropolis/node/core/curator/proto/api"
)
func (l *leaderCurator) UpdateNodeClusterNetworking(ctx context.Context, req *ipb.UpdateNodeClusterNetworkingRequest) (*ipb.UpdateNodeClusterNetworkingResponse, error) {
// Ensure that the given node_id matches the calling node. We currently
// only allow for direct self-reporting of status by nodes.
pi := rpc.GetPeerInfo(ctx)
if pi == nil || pi.Node == nil {
return nil, status.Error(codes.PermissionDenied, "only nodes can update node cluster networking")
}
id := identity.NodeID(pi.Node.PublicKey)
if req.Clusternet == nil {
return nil, status.Error(codes.InvalidArgument, "clusternet must be set")
}
cn := req.Clusternet
if cn.WireguardPubkey == "" {
return nil, status.Error(codes.InvalidArgument, "clusternet.wireguard_pubkey must be set")
}
_, err := wgtypes.ParseKey(cn.WireguardPubkey)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "clusternet.wireguard_pubkey must be a valid wireguard public key")
}
// TODO(q3k): unhardcode this and synchronize with Kubernetes code.
clusterNet := netip.MustParsePrefix("10.0.0.0/16")
// Update node with new clusternetworking data. We're doing a load/modify/store,
// so lock here.
l.muNodes.Lock()
defer l.muNodes.Unlock()
// Retrieve node ...
node, err := nodeLoad(ctx, l.leadership, id)
if err != nil {
return nil, err
}
if node.status == nil {
return nil, status.Error(codes.FailedPrecondition, "node needs to submit at least one status update")
}
externalIP := node.status.ExternalAddress
// Parse/validate given prefixes.
var prefixes []netip.Prefix
for i, prefix := range cn.Prefixes {
// Parse them.
p, err := netip.ParsePrefix(prefix.Cidr)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "clusternet.prefixes[%d].cidr invalid: %v", i, err)
}
// Make sure they're in canonical form.
masked := p.Masked()
if masked.String() != p.String() {
return nil, status.Errorf(codes.InvalidArgument, "clusternet.prefixes[%d].cidr (%s) must be in canonical format (ie. all address bits within the subnet must be zero)", i, p.String())
}
// Make sure they're fully contained within clusterNet or are the /32 of a node's
// externalIP.
okay := false
if clusterNet.Contains(p.Addr()) && p.Bits() >= clusterNet.Bits() {
okay = true
}
if p.IsSingleIP() && p.Addr().String() == externalIP {
okay = true
}
if !okay {
return nil, status.Errorf(codes.InvalidArgument, "clusternet.prefixes[%d].cidr (%s) must be fully contained within cluster network (%s) or be the node's external IP (%s)", i, p.String(), clusterNet.String(), externalIP)
}
prefixes = append(prefixes, p)
}
// ... update its' clusternetworking bits ...
node.wireguardKey = cn.WireguardPubkey
node.networkPrefixes = prefixes
// ... and save it to etcd.
if err := nodeSave(ctx, l.leadership, node); err != nil {
return nil, err
}
return &ipb.UpdateNodeClusterNetworkingResponse{}, nil
}