package webserver import ( "context" "crypto/tls" "crypto/x509" "fmt" "log/slog" "os" "strings" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" pb "git.wntrmute.dev/kyle/metacrypt/gen/metacrypt/v2" ) // VaultClient wraps the gRPC stubs for communicating with the vault. type VaultClient struct { conn *grpc.ClientConn system pb.SystemServiceClient auth pb.AuthServiceClient engine pb.EngineServiceClient pki pb.PKIServiceClient ca pb.CAServiceClient policy pb.PolicyServiceClient sshca pb.SSHCAServiceClient transit pb.TransitServiceClient user pb.UserServiceClient } // NewVaultClient dials the vault gRPC server and returns a client. // NewVaultClient creates a gRPC client to the metacrypt vault API server. // If sni is non-empty, it overrides the TLS server name for certificate // verification (use when the dial address doesn't match a cert SAN). func NewVaultClient(addr, caCertPath, sni string, logger *slog.Logger) (*VaultClient, error) { logger.Debug("connecting to vault", "addr", addr, "ca_cert", caCertPath) tlsCfg := &tls.Config{MinVersion: tls.VersionTLS13} //nolint:gosec // TLS 1.3 minimum if sni != "" { tlsCfg.ServerName = sni } if caCertPath != "" { logger.Debug("loading vault CA certificate", "path", caCertPath) pemData, err := os.ReadFile(caCertPath) //nolint:gosec if err != nil { return nil, fmt.Errorf("webserver: read CA cert: %w", err) } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(pemData) { return nil, fmt.Errorf("webserver: parse CA cert") } tlsCfg.RootCAs = pool logger.Debug("vault CA certificate loaded successfully") } else { logger.Debug("no CA cert configured, using system roots") } logger.Debug("dialing vault gRPC", "addr", addr) conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg))) if err != nil { return nil, fmt.Errorf("webserver: dial vault: %w", err) } logger.Debug("vault gRPC connection established", "addr", addr) return &VaultClient{ conn: conn, system: pb.NewSystemServiceClient(conn), auth: pb.NewAuthServiceClient(conn), engine: pb.NewEngineServiceClient(conn), pki: pb.NewPKIServiceClient(conn), ca: pb.NewCAServiceClient(conn), policy: pb.NewPolicyServiceClient(conn), sshca: pb.NewSSHCAServiceClient(conn), transit: pb.NewTransitServiceClient(conn), user: pb.NewUserServiceClient(conn), }, nil } // Close closes the underlying connection. func (c *VaultClient) Close() error { return c.conn.Close() } // withToken returns a context with the Bearer token in outgoing metadata. func withToken(ctx context.Context, token string) context.Context { return metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token) } // Status returns the current vault state string (e.g. "unsealed"). func (c *VaultClient) Status(ctx context.Context) (string, error) { resp, err := c.system.Status(ctx, &pb.StatusRequest{}) if err != nil { return "", err } return resp.State, nil } // Init initializes the vault with the given password. func (c *VaultClient) Init(ctx context.Context, password string) error { _, err := c.system.Init(ctx, &pb.InitRequest{Password: password}) return err } // Unseal unseals the vault with the given password. func (c *VaultClient) Unseal(ctx context.Context, password string) error { _, err := c.system.Unseal(ctx, &pb.UnsealRequest{Password: password}) return err } // TokenInfo holds validated token details returned by the vault. type TokenInfo struct { Username string Roles []string IsAdmin bool } // Login authenticates against the vault and returns the session token. func (c *VaultClient) Login(ctx context.Context, username, password, totpCode string) (string, error) { resp, err := c.auth.Login(ctx, &pb.LoginRequest{ Username: username, Password: password, TotpCode: totpCode, }) if err != nil { return "", err } return resp.Token, nil } // ValidateToken validates a token against the vault and returns the token info. func (c *VaultClient) ValidateToken(ctx context.Context, token string) (*TokenInfo, error) { resp, err := c.auth.TokenInfo(withToken(ctx, token), &pb.TokenInfoRequest{}) if err != nil { return nil, err } return &TokenInfo{ Username: resp.Username, Roles: resp.Roles, IsAdmin: resp.IsAdmin, }, nil } // MountInfo holds metadata about an engine mount. type MountInfo struct { Name string Type string MountPath string } // ListMounts returns all engine mounts from the vault. func (c *VaultClient) ListMounts(ctx context.Context, token string) ([]MountInfo, error) { resp, err := c.engine.ListMounts(withToken(ctx, token), &pb.ListMountsRequest{}) if err != nil { return nil, err } mounts := make([]MountInfo, 0, len(resp.Mounts)) for _, m := range resp.Mounts { mounts = append(mounts, MountInfo{ Name: m.Name, Type: m.Type, MountPath: m.MountPath, }) } return mounts, nil } // Mount creates a new engine mount on the vault. func (c *VaultClient) Mount(ctx context.Context, token, name, engineType string, config map[string]interface{}) error { req := &pb.MountRequest{ Name: name, Type: engineType, } if len(config) > 0 { cfg := make(map[string]string, len(config)) for k, v := range config { cfg[k] = fmt.Sprintf("%v", v) } req.Config = cfg } _, err := c.engine.Mount(withToken(ctx, token), req) return err } // GetRootCert returns the root CA certificate PEM for the given mount. func (c *VaultClient) GetRootCert(ctx context.Context, mount string) ([]byte, error) { resp, err := c.pki.GetRootCert(ctx, &pb.GetRootCertRequest{Mount: mount}) if err != nil { return nil, err } return resp.CertPem, nil } // GetIssuerCert returns a named issuer certificate PEM for the given mount. func (c *VaultClient) GetIssuerCert(ctx context.Context, mount, issuer string) ([]byte, error) { resp, err := c.pki.GetIssuerCert(ctx, &pb.GetIssuerCertRequest{Mount: mount, Issuer: issuer}) if err != nil { return nil, err } return resp.CertPem, nil } // ImportRoot imports an existing root CA certificate and key into the given mount. func (c *VaultClient) ImportRoot(ctx context.Context, token, mount, certPEM, keyPEM string) error { _, err := c.ca.ImportRoot(withToken(ctx, token), &pb.ImportRootRequest{ Mount: mount, CertPem: []byte(certPEM), KeyPem: []byte(keyPEM), }) return err } // CreateIssuerRequest holds parameters for creating an intermediate CA issuer. type CreateIssuerRequest struct { Mount string Name string KeyAlgorithm string KeySize int32 Expiry string MaxTTL string } // CreateIssuer creates a new intermediate CA issuer on the given mount. func (c *VaultClient) CreateIssuer(ctx context.Context, token string, req CreateIssuerRequest) error { _, err := c.ca.CreateIssuer(withToken(ctx, token), &pb.CreateIssuerRequest{ Mount: req.Mount, Name: req.Name, KeyAlgorithm: req.KeyAlgorithm, KeySize: req.KeySize, Expiry: req.Expiry, MaxTtl: req.MaxTTL, }) return err } // ListIssuers returns the names of all issuers for the given mount. func (c *VaultClient) ListIssuers(ctx context.Context, token, mount string) ([]string, error) { resp, err := c.ca.ListIssuers(withToken(ctx, token), &pb.ListIssuersRequest{Mount: mount}) if err != nil { return nil, err } return resp.Issuers, nil } // IssueCertRequest holds parameters for issuing a leaf certificate. type IssueCertRequest struct { Mount string Issuer string Profile string CommonName string DNSNames []string IPAddresses []string TTL string KeyUsages []string ExtKeyUsages []string } // IssuedCert holds the result of a certificate issuance. type IssuedCert struct { Serial string CertPEM string KeyPEM string ChainPEM string ExpiresAt string } // IssueCert issues a new leaf certificate from the named issuer. func (c *VaultClient) IssueCert(ctx context.Context, token string, req IssueCertRequest) (*IssuedCert, error) { resp, err := c.ca.IssueCert(withToken(ctx, token), &pb.IssueCertRequest{ Mount: req.Mount, Issuer: req.Issuer, Profile: req.Profile, CommonName: req.CommonName, DnsNames: req.DNSNames, IpAddresses: req.IPAddresses, Ttl: req.TTL, KeyUsages: req.KeyUsages, ExtKeyUsages: req.ExtKeyUsages, }) if err != nil { return nil, err } issued := &IssuedCert{ Serial: resp.Serial, CertPEM: string(resp.CertPem), KeyPEM: string(resp.KeyPem), ChainPEM: string(resp.ChainPem), } if resp.ExpiresAt != nil { issued.ExpiresAt = resp.ExpiresAt.AsTime().Format("2006-01-02T15:04:05Z") } return issued, nil } // SignCSRRequest holds parameters for signing an external CSR. type SignCSRRequest struct { Mount string Issuer string CSRPEM string Profile string TTL string } // SignedCert holds the result of signing a CSR. type SignedCert struct { Serial string CertPEM string ChainPEM string ExpiresAt string } // SignCSR signs an externally generated CSR with the named issuer. func (c *VaultClient) SignCSR(ctx context.Context, token string, req SignCSRRequest) (*SignedCert, error) { resp, err := c.ca.SignCSR(withToken(ctx, token), &pb.SignCSRRequest{ Mount: req.Mount, Issuer: req.Issuer, CsrPem: []byte(req.CSRPEM), Profile: req.Profile, Ttl: req.TTL, }) if err != nil { return nil, err } sc := &SignedCert{ Serial: resp.Serial, CertPEM: string(resp.CertPem), ChainPEM: string(resp.ChainPem), } if resp.ExpiresAt != nil { sc.ExpiresAt = resp.ExpiresAt.AsTime().Format("2006-01-02T15:04:05Z") } return sc, nil } // CertDetail holds the full certificate record for the detail view. type CertDetail struct { Serial string Issuer string CommonName string SANs []string Profile string IssuedBy string IssuedAt string ExpiresAt string CertPEM string Revoked bool RevokedAt string RevokedBy string } // GetCert retrieves a full certificate record by serial number. func (c *VaultClient) GetCert(ctx context.Context, token, mount, serial string) (*CertDetail, error) { resp, err := c.ca.GetCert(withToken(ctx, token), &pb.GetCertRequest{Mount: mount, Serial: serial}) if err != nil { return nil, err } rec := resp.GetCert() if rec == nil { return nil, fmt.Errorf("cert not found") } cd := &CertDetail{ Serial: rec.Serial, Issuer: rec.Issuer, CommonName: rec.CommonName, SANs: rec.Sans, Profile: rec.Profile, IssuedBy: rec.IssuedBy, CertPEM: string(rec.CertPem), Revoked: rec.Revoked, RevokedBy: rec.RevokedBy, } if rec.IssuedAt != nil { cd.IssuedAt = rec.IssuedAt.AsTime().Format("2006-01-02T15:04:05Z") } if rec.ExpiresAt != nil { cd.ExpiresAt = rec.ExpiresAt.AsTime().Format("2006-01-02T15:04:05Z") } if rec.RevokedAt != nil { cd.RevokedAt = rec.RevokedAt.AsTime().Format("2006-01-02T15:04:05Z") } return cd, nil } // RevokeCert marks a certificate as revoked. func (c *VaultClient) RevokeCert(ctx context.Context, token, mount, serial string) error { _, err := c.ca.RevokeCert(withToken(ctx, token), &pb.RevokeCertRequest{Mount: mount, Serial: serial}) return err } // DeleteCert permanently removes a certificate record. func (c *VaultClient) DeleteCert(ctx context.Context, token, mount, serial string) error { _, err := c.ca.DeleteCert(withToken(ctx, token), &pb.DeleteCertRequest{Mount: mount, Serial: serial}) return err } // PolicyRule holds a policy rule for display and management. type PolicyRule struct { ID string Priority int Effect string Usernames []string Roles []string Resources []string Actions []string } // ListPolicies returns all policy rules from the vault. func (c *VaultClient) ListPolicies(ctx context.Context, token string) ([]PolicyRule, error) { resp, err := c.policy.ListPolicies(withToken(ctx, token), &pb.ListPoliciesRequest{}) if err != nil { return nil, err } rules := make([]PolicyRule, 0, len(resp.Rules)) for _, r := range resp.Rules { rules = append(rules, pbToRule(r)) } return rules, nil } // GetPolicy retrieves a single policy rule by ID. func (c *VaultClient) GetPolicy(ctx context.Context, token, id string) (*PolicyRule, error) { resp, err := c.policy.GetPolicy(withToken(ctx, token), &pb.GetPolicyRequest{Id: id}) if err != nil { return nil, err } rule := pbToRule(resp.Rule) return &rule, nil } // CreatePolicy creates a new policy rule. func (c *VaultClient) CreatePolicy(ctx context.Context, token string, rule PolicyRule) (*PolicyRule, error) { resp, err := c.policy.CreatePolicy(withToken(ctx, token), &pb.CreatePolicyRequest{ Rule: ruleToPB(rule), }) if err != nil { return nil, err } created := pbToRule(resp.Rule) return &created, nil } // DeletePolicy removes a policy rule by ID. func (c *VaultClient) DeletePolicy(ctx context.Context, token, id string) error { _, err := c.policy.DeletePolicy(withToken(ctx, token), &pb.DeletePolicyRequest{Id: id}) return err } func pbToRule(r *pb.PolicyRule) PolicyRule { if r == nil { return PolicyRule{} } return PolicyRule{ ID: r.Id, Priority: int(r.Priority), Effect: r.Effect, Usernames: r.Usernames, Roles: r.Roles, Resources: r.Resources, Actions: r.Actions, } } func ruleToPB(r PolicyRule) *pb.PolicyRule { return &pb.PolicyRule{ Id: r.ID, Priority: int32(r.Priority), Effect: r.Effect, Usernames: r.Usernames, Roles: r.Roles, Resources: r.Resources, Actions: r.Actions, } } // CertSummary holds lightweight certificate metadata for list views. type CertSummary struct { Serial string Issuer string CommonName string Profile string IssuedBy string IssuedAt string ExpiresAt string } // ListCerts returns all certificate summaries for the given mount. func (c *VaultClient) ListCerts(ctx context.Context, token, mount string) ([]CertSummary, error) { resp, err := c.ca.ListCerts(withToken(ctx, token), &pb.ListCertsRequest{Mount: mount}) if err != nil { return nil, err } certs := make([]CertSummary, 0, len(resp.Certs)) for _, s := range resp.Certs { cs := CertSummary{ Serial: s.Serial, Issuer: s.Issuer, CommonName: s.CommonName, Profile: s.Profile, IssuedBy: s.IssuedBy, } if s.IssuedAt != nil { cs.IssuedAt = s.IssuedAt.AsTime().Format("2006-01-02T15:04:05Z") } if s.ExpiresAt != nil { cs.ExpiresAt = s.ExpiresAt.AsTime().Format("2006-01-02T15:04:05Z") } certs = append(certs, cs) } return certs, nil } // --------------------------------------------------------------------------- // SSH CA // --------------------------------------------------------------------------- // SSHCAPublicKey holds the CA public key details for display. type SSHCAPublicKey struct { PublicKey string // authorized_keys format } // GetSSHCAPublicKey returns the SSH CA public key for a mount. func (c *VaultClient) GetSSHCAPublicKey(ctx context.Context, mount string) (*SSHCAPublicKey, error) { resp, err := c.sshca.GetCAPublicKey(ctx, &pb.SSHGetCAPublicKeyRequest{Mount: mount}) if err != nil { return nil, err } return &SSHCAPublicKey{PublicKey: resp.PublicKey}, nil } // SSHCASignRequest holds parameters for signing an SSH certificate. type SSHCASignRequest struct { PublicKey string Principals []string Profile string TTL string } // SSHCASignResult holds the result of signing an SSH certificate. type SSHCASignResult struct { Serial string CertType string Principals []string CertData string KeyID string IssuedBy string IssuedAt string ExpiresAt string } func sshSignResultFromHost(resp *pb.SSHSignHostResponse) *SSHCASignResult { r := &SSHCASignResult{ Serial: resp.Serial, CertType: resp.CertType, Principals: resp.Principals, CertData: resp.CertData, KeyID: resp.KeyId, IssuedBy: resp.IssuedBy, } if resp.IssuedAt != nil { r.IssuedAt = resp.IssuedAt.AsTime().Format("2006-01-02T15:04:05Z") } if resp.ExpiresAt != nil { r.ExpiresAt = resp.ExpiresAt.AsTime().Format("2006-01-02T15:04:05Z") } return r } func sshSignResultFromUser(resp *pb.SSHSignUserResponse) *SSHCASignResult { r := &SSHCASignResult{ Serial: resp.Serial, CertType: resp.CertType, Principals: resp.Principals, CertData: resp.CertData, KeyID: resp.KeyId, IssuedBy: resp.IssuedBy, } if resp.IssuedAt != nil { r.IssuedAt = resp.IssuedAt.AsTime().Format("2006-01-02T15:04:05Z") } if resp.ExpiresAt != nil { r.ExpiresAt = resp.ExpiresAt.AsTime().Format("2006-01-02T15:04:05Z") } return r } // SSHCASignHost signs an SSH host certificate. func (c *VaultClient) SSHCASignHost(ctx context.Context, token, mount string, req SSHCASignRequest) (*SSHCASignResult, error) { hostname := "" if len(req.Principals) > 0 { hostname = req.Principals[0] } resp, err := c.sshca.SignHost(withToken(ctx, token), &pb.SSHSignHostRequest{ Mount: mount, PublicKey: req.PublicKey, Hostname: hostname, Ttl: req.TTL, }) if err != nil { return nil, err } return sshSignResultFromHost(resp), nil } // SSHCASignUser signs an SSH user certificate. func (c *VaultClient) SSHCASignUser(ctx context.Context, token, mount string, req SSHCASignRequest) (*SSHCASignResult, error) { resp, err := c.sshca.SignUser(withToken(ctx, token), &pb.SSHSignUserRequest{ Mount: mount, PublicKey: req.PublicKey, Principals: req.Principals, Profile: req.Profile, Ttl: req.TTL, }) if err != nil { return nil, err } return sshSignResultFromUser(resp), nil } // SSHCAProfileSummary holds lightweight profile data for list views. type SSHCAProfileSummary struct { Name string } // ListSSHCAProfiles returns all signing profile names for a mount. func (c *VaultClient) ListSSHCAProfiles(ctx context.Context, token, mount string) ([]SSHCAProfileSummary, error) { resp, err := c.sshca.ListProfiles(withToken(ctx, token), &pb.SSHListProfilesRequest{Mount: mount}) if err != nil { return nil, err } profiles := make([]SSHCAProfileSummary, 0, len(resp.Profiles)) for _, name := range resp.Profiles { profiles = append(profiles, SSHCAProfileSummary{Name: name}) } return profiles, nil } // SSHCAProfile holds full profile data for the detail view. type SSHCAProfile struct { Name string CriticalOptions map[string]string Extensions map[string]string MaxTTL string AllowedPrincipals []string } // GetSSHCAProfile retrieves a signing profile by name. func (c *VaultClient) GetSSHCAProfile(ctx context.Context, token, mount, name string) (*SSHCAProfile, error) { resp, err := c.sshca.GetProfile(withToken(ctx, token), &pb.SSHGetProfileRequest{Mount: mount, Name: name}) if err != nil { return nil, err } return &SSHCAProfile{ Name: resp.Name, CriticalOptions: resp.CriticalOptions, Extensions: resp.Extensions, MaxTTL: resp.MaxTtl, AllowedPrincipals: resp.AllowedPrincipals, }, nil } // SSHCAProfileRequest holds parameters for creating or updating a profile. type SSHCAProfileRequest struct { Name string CriticalOptions map[string]string Extensions map[string]string MaxTTL string AllowedPrincipals []string } // CreateSSHCAProfile creates a new signing profile. func (c *VaultClient) CreateSSHCAProfile(ctx context.Context, token, mount string, req SSHCAProfileRequest) error { _, err := c.sshca.CreateProfile(withToken(ctx, token), &pb.SSHCreateProfileRequest{ Mount: mount, Name: req.Name, CriticalOptions: req.CriticalOptions, Extensions: req.Extensions, MaxTtl: req.MaxTTL, AllowedPrincipals: req.AllowedPrincipals, }) return err } // UpdateSSHCAProfile updates an existing signing profile. func (c *VaultClient) UpdateSSHCAProfile(ctx context.Context, token, mount, name string, req SSHCAProfileRequest) error { _, err := c.sshca.UpdateProfile(withToken(ctx, token), &pb.SSHUpdateProfileRequest{ Mount: mount, Name: name, CriticalOptions: req.CriticalOptions, Extensions: req.Extensions, MaxTtl: req.MaxTTL, AllowedPrincipals: req.AllowedPrincipals, }) return err } // DeleteSSHCAProfile removes a signing profile. func (c *VaultClient) DeleteSSHCAProfile(ctx context.Context, token, mount, name string) error { _, err := c.sshca.DeleteProfile(withToken(ctx, token), &pb.SSHDeleteProfileRequest{Mount: mount, Name: name}) return err } // SSHCACertSummary holds lightweight cert data for list views. type SSHCACertSummary struct { Serial string CertType string Principals string KeyID string Profile string IssuedBy string IssuedAt string ExpiresAt string Revoked bool } // ListSSHCACerts returns all SSH certificate summaries for a mount. func (c *VaultClient) ListSSHCACerts(ctx context.Context, token, mount string) ([]SSHCACertSummary, error) { resp, err := c.sshca.ListCerts(withToken(ctx, token), &pb.SSHListCertsRequest{Mount: mount}) if err != nil { return nil, err } certs := make([]SSHCACertSummary, 0, len(resp.Certs)) for _, s := range resp.Certs { cs := SSHCACertSummary{ Serial: s.Serial, CertType: s.CertType, Principals: strings.Join(s.Principals, ", "), KeyID: s.KeyId, Profile: s.Profile, IssuedBy: s.IssuedBy, Revoked: s.Revoked, } if s.IssuedAt != nil { cs.IssuedAt = s.IssuedAt.AsTime().Format("2006-01-02T15:04:05Z") } if s.ExpiresAt != nil { cs.ExpiresAt = s.ExpiresAt.AsTime().Format("2006-01-02T15:04:05Z") } certs = append(certs, cs) } return certs, nil } // SSHCACertDetail holds full SSH certificate data for the detail view. type SSHCACertDetail struct { Serial string CertType string Principals []string CertData string KeyID string Profile string IssuedBy string IssuedAt string ExpiresAt string Revoked bool RevokedAt string RevokedBy string } // GetSSHCACert retrieves a full SSH certificate record by serial. func (c *VaultClient) GetSSHCACert(ctx context.Context, token, mount, serial string) (*SSHCACertDetail, error) { resp, err := c.sshca.GetCert(withToken(ctx, token), &pb.SSHGetCertRequest{Mount: mount, Serial: serial}) if err != nil { return nil, err } rec := resp.GetCert() if rec == nil { return nil, fmt.Errorf("cert not found") } cd := &SSHCACertDetail{ Serial: rec.Serial, CertType: rec.CertType, Principals: rec.Principals, CertData: rec.CertData, KeyID: rec.KeyId, Profile: rec.Profile, IssuedBy: rec.IssuedBy, Revoked: rec.Revoked, RevokedBy: rec.RevokedBy, } if rec.IssuedAt != nil { cd.IssuedAt = rec.IssuedAt.AsTime().Format("2006-01-02T15:04:05Z") } if rec.ExpiresAt != nil { cd.ExpiresAt = rec.ExpiresAt.AsTime().Format("2006-01-02T15:04:05Z") } if rec.RevokedAt != nil { cd.RevokedAt = rec.RevokedAt.AsTime().Format("2006-01-02T15:04:05Z") } return cd, nil } // RevokeSSHCACert marks an SSH certificate as revoked. func (c *VaultClient) RevokeSSHCACert(ctx context.Context, token, mount, serial string) error { _, err := c.sshca.RevokeCert(withToken(ctx, token), &pb.SSHRevokeCertRequest{Mount: mount, Serial: serial}) return err } // DeleteSSHCACert permanently removes an SSH certificate record. func (c *VaultClient) DeleteSSHCACert(ctx context.Context, token, mount, serial string) error { _, err := c.sshca.DeleteCert(withToken(ctx, token), &pb.SSHDeleteCertRequest{Mount: mount, Serial: serial}) return err } // GetSSHCAKRL returns the binary KRL data for a mount. func (c *VaultClient) GetSSHCAKRL(ctx context.Context, mount string) ([]byte, error) { resp, err := c.sshca.GetKRL(ctx, &pb.SSHGetKRLRequest{Mount: mount}) if err != nil { return nil, err } return resp.Krl, nil } // --------------------------------------------------------------------------- // Transit // --------------------------------------------------------------------------- // TransitKeySummary holds lightweight key data for list views. type TransitKeySummary struct { Name string } // ListTransitKeys returns all key names for a mount. func (c *VaultClient) ListTransitKeys(ctx context.Context, token, mount string) ([]TransitKeySummary, error) { resp, err := c.transit.ListKeys(withToken(ctx, token), &pb.ListTransitKeysRequest{Mount: mount}) if err != nil { return nil, err } keys := make([]TransitKeySummary, 0, len(resp.Keys)) for _, name := range resp.Keys { keys = append(keys, TransitKeySummary{Name: name}) } return keys, nil } // TransitKeyDetail holds full key metadata for the detail view. type TransitKeyDetail struct { Name string Type string CurrentVersion int MinDecryptionVersion int AllowDeletion bool Versions []int } // GetTransitKey retrieves key metadata. func (c *VaultClient) GetTransitKey(ctx context.Context, token, mount, name string) (*TransitKeyDetail, error) { resp, err := c.transit.GetKey(withToken(ctx, token), &pb.GetTransitKeyRequest{Mount: mount, Name: name}) if err != nil { return nil, err } versions := make([]int, 0, len(resp.Versions)) for _, v := range resp.Versions { versions = append(versions, int(v)) } return &TransitKeyDetail{ Name: resp.Name, Type: resp.Type, CurrentVersion: int(resp.CurrentVersion), MinDecryptionVersion: int(resp.MinDecryptionVersion), AllowDeletion: resp.AllowDeletion, Versions: versions, }, nil } // CreateTransitKey creates a new named key. func (c *VaultClient) CreateTransitKey(ctx context.Context, token, mount, name, keyType string) error { _, err := c.transit.CreateKey(withToken(ctx, token), &pb.CreateTransitKeyRequest{ Mount: mount, Name: name, Type: keyType, }) return err } // DeleteTransitKey permanently removes a named key. func (c *VaultClient) DeleteTransitKey(ctx context.Context, token, mount, name string) error { _, err := c.transit.DeleteKey(withToken(ctx, token), &pb.DeleteTransitKeyRequest{Mount: mount, Name: name}) return err } // RotateTransitKey creates a new version of the named key. func (c *VaultClient) RotateTransitKey(ctx context.Context, token, mount, name string) error { _, err := c.transit.RotateKey(withToken(ctx, token), &pb.RotateTransitKeyRequest{Mount: mount, Name: name}) return err } // UpdateTransitKeyConfig updates key config (min_decryption_version, allow_deletion). func (c *VaultClient) UpdateTransitKeyConfig(ctx context.Context, token, mount, name string, minDecryptVersion int, allowDeletion bool) error { _, err := c.transit.UpdateKeyConfig(withToken(ctx, token), &pb.UpdateTransitKeyConfigRequest{ Mount: mount, Name: name, MinDecryptionVersion: int32(minDecryptVersion), AllowDeletion: allowDeletion, }) return err } // TrimTransitKey deletes old key versions below min_decryption_version. func (c *VaultClient) TrimTransitKey(ctx context.Context, token, mount, name string) (int, error) { resp, err := c.transit.TrimKey(withToken(ctx, token), &pb.TrimTransitKeyRequest{Mount: mount, Name: name}) if err != nil { return 0, err } return int(resp.Trimmed), nil } // TransitEncrypt encrypts plaintext with a named key. func (c *VaultClient) TransitEncrypt(ctx context.Context, token, mount, key, plaintext, transitCtx string) (string, error) { resp, err := c.transit.Encrypt(withToken(ctx, token), &pb.TransitEncryptRequest{ Mount: mount, Key: key, Plaintext: plaintext, Context: transitCtx, }) if err != nil { return "", err } return resp.Ciphertext, nil } // TransitDecrypt decrypts ciphertext with a named key. func (c *VaultClient) TransitDecrypt(ctx context.Context, token, mount, key, ciphertext, transitCtx string) (string, error) { resp, err := c.transit.Decrypt(withToken(ctx, token), &pb.TransitDecryptRequest{ Mount: mount, Key: key, Ciphertext: ciphertext, Context: transitCtx, }) if err != nil { return "", err } return resp.Plaintext, nil } // TransitRewrap re-encrypts ciphertext with the latest key version. func (c *VaultClient) TransitRewrap(ctx context.Context, token, mount, key, ciphertext, transitCtx string) (string, error) { resp, err := c.transit.Rewrap(withToken(ctx, token), &pb.TransitRewrapRequest{ Mount: mount, Key: key, Ciphertext: ciphertext, Context: transitCtx, }) if err != nil { return "", err } return resp.Ciphertext, nil } // TransitSign signs input data with an asymmetric key. func (c *VaultClient) TransitSign(ctx context.Context, token, mount, key, input string) (string, error) { resp, err := c.transit.Sign(withToken(ctx, token), &pb.TransitSignRequest{ Mount: mount, Key: key, Input: input, }) if err != nil { return "", err } return resp.Signature, nil } // TransitVerify verifies a signature against input data. func (c *VaultClient) TransitVerify(ctx context.Context, token, mount, key, input, signature string) (bool, error) { resp, err := c.transit.Verify(withToken(ctx, token), &pb.TransitVerifyRequest{ Mount: mount, Key: key, Input: input, Signature: signature, }) if err != nil { return false, err } return resp.Valid, nil } // TransitHMAC computes an HMAC. func (c *VaultClient) TransitHMAC(ctx context.Context, token, mount, key, input string) (string, error) { resp, err := c.transit.Hmac(withToken(ctx, token), &pb.TransitHmacRequest{ Mount: mount, Key: key, Input: input, }) if err != nil { return "", err } return resp.Hmac, nil } // GetTransitPublicKey returns the public key for an asymmetric transit key. func (c *VaultClient) GetTransitPublicKey(ctx context.Context, token, mount, name string) (string, error) { resp, err := c.transit.GetPublicKey(withToken(ctx, token), &pb.GetTransitPublicKeyRequest{Mount: mount, Name: name}) if err != nil { return "", err } return resp.PublicKey, nil } // --------------------------------------------------------------------------- // User (E2E Encryption) // --------------------------------------------------------------------------- // UserKeyInfo holds a user's public key details. type UserKeyInfo struct { Username string PublicKey string Algorithm string } // UserRegister self-registers the caller. func (c *VaultClient) UserRegister(ctx context.Context, token, mount string) (*UserKeyInfo, error) { resp, err := c.user.Register(withToken(ctx, token), &pb.UserRegisterRequest{Mount: mount}) if err != nil { return nil, err } return &UserKeyInfo{ Username: resp.Username, PublicKey: resp.PublicKey, Algorithm: resp.Algorithm, }, nil } // UserProvision creates a keypair for a given username. Admin only. func (c *VaultClient) UserProvision(ctx context.Context, token, mount, username string) (*UserKeyInfo, error) { resp, err := c.user.Provision(withToken(ctx, token), &pb.UserProvisionRequest{Mount: mount, Username: username}) if err != nil { return nil, err } return &UserKeyInfo{ Username: resp.Username, PublicKey: resp.PublicKey, Algorithm: resp.Algorithm, }, nil } // GetUserPublicKey returns the public key for a username. func (c *VaultClient) GetUserPublicKey(ctx context.Context, token, mount, username string) (*UserKeyInfo, error) { resp, err := c.user.GetPublicKey(withToken(ctx, token), &pb.UserGetPublicKeyRequest{Mount: mount, Username: username}) if err != nil { return nil, err } return &UserKeyInfo{ Username: resp.Username, PublicKey: resp.PublicKey, Algorithm: resp.Algorithm, }, nil } // ListUsers returns all registered usernames. func (c *VaultClient) ListUsers(ctx context.Context, token, mount string) ([]string, error) { resp, err := c.user.ListUsers(withToken(ctx, token), &pb.UserListUsersRequest{Mount: mount}) if err != nil { return nil, err } return resp.Users, nil } // UserEncrypt encrypts plaintext for one or more recipients. func (c *VaultClient) UserEncrypt(ctx context.Context, token, mount, plaintext, userMetadata string, recipients []string) (string, error) { resp, err := c.user.Encrypt(withToken(ctx, token), &pb.UserEncryptRequest{ Mount: mount, Plaintext: plaintext, Metadata: userMetadata, Recipients: recipients, }) if err != nil { return "", err } return resp.Envelope, nil } // UserDecryptResult holds the result of decrypting an envelope. type UserDecryptResult struct { Plaintext string Sender string Metadata string } // UserDecrypt decrypts an envelope addressed to the caller. func (c *VaultClient) UserDecrypt(ctx context.Context, token, mount, envelope string) (*UserDecryptResult, error) { resp, err := c.user.Decrypt(withToken(ctx, token), &pb.UserDecryptRequest{Mount: mount, Envelope: envelope}) if err != nil { return nil, err } return &UserDecryptResult{ Plaintext: resp.Plaintext, Sender: resp.Sender, Metadata: resp.Metadata, }, nil } // UserReEncrypt re-encrypts an envelope with current keys. func (c *VaultClient) UserReEncrypt(ctx context.Context, token, mount, envelope string) (string, error) { resp, err := c.user.ReEncrypt(withToken(ctx, token), &pb.UserReEncryptRequest{Mount: mount, Envelope: envelope}) if err != nil { return "", err } return resp.Envelope, nil } // UserRotateKey generates a new keypair for the caller. func (c *VaultClient) UserRotateKey(ctx context.Context, token, mount string) (*UserKeyInfo, error) { resp, err := c.user.RotateKey(withToken(ctx, token), &pb.UserRotateKeyRequest{Mount: mount}) if err != nil { return nil, err } return &UserKeyInfo{ Username: resp.Username, PublicKey: resp.PublicKey, Algorithm: resp.Algorithm, }, nil } // UserDeleteUser removes a user's keys. Admin only. func (c *VaultClient) UserDeleteUser(ctx context.Context, token, mount, username string) error { _, err := c.user.DeleteUser(withToken(ctx, token), &pb.UserDeleteUserRequest{Mount: mount, Username: username}) return err }