~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
161 lines · 6.6 KB Raw History
package gateway

import (
	"context"
	"strings"
	"time"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/app/compose"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/app/marketrequester"
	contractvault "github.com/johnandersen777/socialweb-computer-kcp/internal/contract/vault"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime/manifests"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/clock"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/opt"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/openbaoctl"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/vaultlayout"
)

const (
	RootVaultAsset     = "bao"
	rootVaultAppRole   = "approle"
	rootVaultPKIMount  = "pki"
	rootVaultKVMount   = "secret"
	rootVaultPKIMaxTTL = 8760 * time.Hour
)

func keyType(kind string) string {
	switch strings.ToLower(kind) {
	case "ecdsa", "ec":
		return "ec"
	}
	return kind
}

func RootVaultWriterRules() []v1alpha1.OpenBaoPolicyRule {
	cu := []string{"create", "update"}
	return []v1alpha1.OpenBaoPolicyRule{
		{Path: rootVaultPKIMount + "/root/generate/internal", Capabilities: cu},
		{Path: rootVaultPKIMount + "/cert/ca", Capabilities: []string{"read"}},
		{Path: rootVaultKVMount + "/data/socialweb/*", Capabilities: []string{"create", "read", "update"}},
	}
}

func RootVaultDeclared() *v1alpha1.OpenBaoServerDeclared {
	layout := vaultlayout.Default()
	d := &v1alpha1.OpenBaoServerDeclared{}
	for _, m := range layout.Mounts {
		if m.Vault != vaultlayout.Root {
			continue
		}
		sm := v1alpha1.OpenBaoSecretMount{Path: strings.Trim(m.Path, "/"), Type: string(m.Kind)}
		if m.Kind == vaultlayout.MountPKI {
			sm.MaxLeaseTTL = opt.Positive(m.MaxLeaseTTL, rootVaultPKIMaxTTL).String()
		}
		d.SecretMounts = append(d.SecretMounts, sm)
	}
	d.AuthMounts = append(d.AuthMounts, v1alpha1.OpenBaoAuthMount{Path: rootVaultAppRole, Type: "approle"})
	for _, p := range layout.Policies {
		if p.Vault == vaultlayout.Root {
			d.Policies = append(d.Policies, v1alpha1.OpenBaoPolicy{Name: p.Name, HCL: p.HCL})
		}
	}
	d.Policies = append(d.Policies, v1alpha1.OpenBaoPolicy{Name: vaultlayout.PolicyRootWriter, Rules: RootVaultWriterRules()})
	for _, r := range layout.AppRoles {
		if r.Vault == vaultlayout.Root {
			d.AppRoles = append(d.AppRoles, v1alpha1.OpenBaoAppRole{Mount: rootVaultAppRole, Name: r.Name, Policies: r.Policies,
				TokenTTL: clock.DurationString(r.TokenTTL), TokenMaxTTL: clock.DurationString(r.TokenMaxTTL), SecretIDTTL: clock.DurationString(r.SecretIDTTL)})
		}
	}
	for _, r := range layout.PKIRoles {
		if r.Vault != vaultlayout.Root {
			continue
		}
		d.PKIRoles = append(d.PKIRoles, v1alpha1.OpenBaoPKIRole{Mount: strings.Trim(r.Mount, "/"), Name: r.Name,
			AllowedDomains: r.AllowedDomains, AllowSubdomains: r.AllowSubdomains, AllowBareDomains: r.AllowBareDomains,
			AllowIPSANs: r.AllowIPSANs, EnforceHostnames: r.EnforceHostnames, RequireCN: r.RequireCN, ClientFlag: r.ClientAuth,
			ServerFlag: r.ServerAuth, KeyType: keyType(r.KeyType), KeyBits: int32(r.KeyBits), KeyUsage: r.KeyUsage,
			TTL: clock.DurationString(r.TTL), MaxTTL: clock.DurationString(r.MaxTTL), NoStore: true})
	}
	for _, m := range d.SecretMounts {
		if m.Type == string(vaultlayout.MountPKI) {
			d.PKITidy = append(d.PKITidy, v1alpha1.OpenBaoPKITidy{Mount: m.Path})
		}
	}
	return d
}

func RequesterVaultRules() []v1alpha1.OpenBaoPolicyRule {
	return []v1alpha1.OpenBaoPolicyRule{
		{Path: WebAppVaultMount + "/data/contracts/*", Capabilities: []string{"read"}},
	}
}

func RequesterKeysPath() string {
	return "identity/" + marketrequester.RequesterIdentity + "/keys"
}

func RootVaultSpec(g v1alpha1.GatewaySpec) (v1alpha1.OpenBaoServerSpec, error) {
	declared := RootVaultDeclared()
	declared.Policies = append(declared.Policies, v1alpha1.OpenBaoPolicy{Name: GatewayRequesterPolicy, Rules: RequesterVaultRules()},
		v1alpha1.OpenBaoPolicy{Name: GatewayPdsPolicy, Rules: []v1alpha1.OpenBaoPolicyRule{
			{Path: RequesterVaultMount + "/data/" + RequesterKeysPath(), Capabilities: []string{"read"}}}})
	declared.Policies = append(declared.Policies, compose.AtprotoPolicies()...)
	plugin, err := compose.AtprotoPlugin(g.Options, g.Network, compose.GatewayAtprotoScope())
	if err != nil {
		return v1alpha1.OpenBaoServerSpec{}, err
	}
	return v1alpha1.OpenBaoServerSpec{Asset: &v1alpha1.HostProcessAsset{Name: RootVaultAsset},
		Clients: []v1alpha1.OpenBaoServerClient{{Name: vaultlayout.RootClient, Policies: []string{vaultlayout.PolicyRootWriter}},
			{Name: WebAppVaultClient, KVMount: WebAppVaultMount},
			{Name: marketrequester.RequesterVaultClient, KVMount: RequesterVaultMount, Policies: []string{GatewayRequesterPolicy}},
			{Name: compose.PdsVaultClient, Policies: []string{GatewayPdsPolicy}},
			{Name: contractvault.AtprotoSessionClient, Policies: []string{compose.AtprotoSessionProxyPolicy}}},
		Declared: declared, Plugins: []v1alpha1.OpenBaoPlugin{plugin}}, nil
}

func (p *pass) rootVault(ctx context.Context, onlyExisting bool) (*v1alpha1.OpenBaoServer, error) {
	c, r := p.c, p.r
	if onlyExisting {
		if _, err := compose.Get[metav1.PartialObjectMetadata](ctx, c, r.Workspace, compose.OpenBaoServerGVR, vaultlayout.RootServerName); apierrors.IsNotFound(err) {
			return nil, nil
		} else if err != nil {
			return nil, err
		}
	}
	want, err := RootVaultSpec(p.o.Spec)
	if err != nil {
		return nil, err
	}
	obj := &v1alpha1.OpenBaoServer{ObjectMeta: metav1.ObjectMeta{Name: vaultlayout.RootServerName}, Spec: want}
	if err := c.ApplyTyped(ctx, r, r.Workspace, compose.OpenBaoServerGVR, v1alpha1.GroupVersion.String(), compose.KindOpenBaoServer, obj); err != nil {
		return nil, err
	}
	if err := c.ApplyVault(ctx, r, vaultlayout.RootServerName); err != nil {
		return nil, err
	}
	return compose.Get[v1alpha1.OpenBaoServer](ctx, c, r.Workspace, compose.OpenBaoServerGVR, vaultlayout.RootServerName)
}

func clientVaultOf(o *v1alpha1.OpenBaoServer, client string) (manifests.GatewayVault, bool) {
	if o == nil || !openbaoctl.Synced(o) || o.Status.Refs == nil || o.Status.Refs.CA == "" {
		return manifests.GatewayVault{}, false
	}
	for _, cl := range o.Status.Clients {
		if cl.Name == client && cl.ClientCert != "" {
			return manifests.GatewayVault{Server: o.Name, Client: cl.Name}, true
		}
	}
	return manifests.GatewayVault{}, false
}

func WebAppVaultOf(o *v1alpha1.OpenBaoServer) (manifests.GatewayVault, bool) {
	return clientVaultOf(o, WebAppVaultClient)
}

func RequesterVaultOf(o *v1alpha1.OpenBaoServer) (manifests.GatewayVault, bool) {
	return clientVaultOf(o, marketrequester.RequesterVaultClient)
}