helpers/akeyless/service/akeyless.go (286 lines of code) (raw):

package service import ( "context" "encoding/base64" "errors" "fmt" "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/aws" "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/azure" "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/gcp" akeyless_api "github.com/akeylesslabs/akeyless-go/v4" "gitlab.com/gitlab-org/gitlab-runner/common" ) type akeylessAPIClient interface { GetSecretValue(ctx context.Context, body akeyless_api.GetSecretValue) (map[string]any, error) Auth(ctx context.Context, params akeyless_api.Auth) (akeyless_api.AuthOutput, error) DescribeItem(ctx context.Context, params akeyless_api.DescribeItem) (akeyless_api.Item, error) GetDynamicSecretValue(ctx context.Context, params akeyless_api.GetDynamicSecretValue) (map[string]any, error) GetRotatedSecretValue(ctx context.Context, params akeyless_api.GetRotatedSecretValue) (map[string]any, error) GetSSHCertificate(ctx context.Context, params akeyless_api.GetSSHCertificate) (akeyless_api.GetSSHCertificateOutput, error) GetPKICertificate(ctx context.Context, params akeyless_api.GetPKICertificate) (akeyless_api.GetPKICertificateOutput, error) } type akeylessClient struct { api *akeyless_api.V2ApiService } func newClient(secret *common.AkeylessSecret) *akeylessClient { apiService := akeyless_api.NewAPIClient(&akeyless_api.Configuration{ Servers: []akeyless_api.ServerConfiguration{{URL: secret.Server.AkeylessApiUrl}}, DefaultHeader: map[string]string{"akeylessclienttype": "gitlab"}, }).V2Api return &akeylessClient{ api: apiService, } } func (c *akeylessClient) GetSecretValue(ctx context.Context, body akeyless_api.GetSecretValue) (map[string]any, error) { resp, _, err := c.api.GetSecretValue(ctx).Body(body).Execute() return resp, err } func (c *akeylessClient) Auth(ctx context.Context, body akeyless_api.Auth) (akeyless_api.AuthOutput, error) { out, _, err := c.api.Auth(ctx).Body(body).Execute() return out, err } func (c *akeylessClient) DescribeItem(ctx context.Context, body akeyless_api.DescribeItem) (akeyless_api.Item, error) { out, _, err := c.api.DescribeItem(ctx).Body(body).Execute() return out, err } func (c *akeylessClient) GetDynamicSecretValue(ctx context.Context, body akeyless_api.GetDynamicSecretValue) (map[string]any, error) { resp, _, err := c.api.GetDynamicSecretValue(ctx).Body(body).Execute() return resp, err } func (c *akeylessClient) GetRotatedSecretValue(ctx context.Context, body akeyless_api.GetRotatedSecretValue) (map[string]any, error) { resp, _, err := c.api.GetRotatedSecretValue(ctx).Body(body).Execute() return resp, err } func (c *akeylessClient) GetSSHCertificate(ctx context.Context, body akeyless_api.GetSSHCertificate) (akeyless_api.GetSSHCertificateOutput, error) { resp, _, err := c.api.GetSSHCertificate(ctx).Body(body).Execute() return resp, err } func (c *akeylessClient) GetPKICertificate(ctx context.Context, body akeyless_api.GetPKICertificate) (akeyless_api.GetPKICertificateOutput, error) { resp, _, err := c.api.GetPKICertificate(ctx).Body(body).Execute() return resp, err } type AccessType string const ( AccessTypeApiKey AccessType = "api_key" AccessTypeAwsIAM AccessType = "aws_iam" AccessTypeAzureAd AccessType = "azure_ad" AccessTypeGCP AccessType = "gcp" AccessTypeUid AccessType = "universal_identity" AccessTypeK8S AccessType = "k8s" AccessTypeJWT AccessType = "jwt" ) type ItemType string const ( ItemTypeStaticSecret ItemType = "STATIC_SECRET" ItemTypeDynamicSecret ItemType = "DYNAMIC_SECRET" ItemTypeRotatedSecret ItemType = "ROTATED_SECRET" ItemTypeSSHCertIssuer ItemType = "SSH_CERT_ISSUER" ItemTypePkiCertIssuer ItemType = "PKI_CERT_ISSUER" ) type Config struct { AccessType AccessType AccessId string AccessKey string ApiURL string AzureObjectId string Path string GcpAudience string UidToken string K8SServiceAccountToken string K8SAuthConfigName string JWT string } type Akeyless interface { GetSecret(ctx context.Context) (any, error) } type AkeylessAPI struct { secret *common.AkeylessSecret client akeylessAPIClient } func NewAkeyless(secret *common.AkeylessSecret) *AkeylessAPI { return &AkeylessAPI{ secret: secret, client: newClient(secret), } } func (v *AkeylessAPI) GetSecret(ctx context.Context) (any, error) { token := v.secret.Server.AkeylessToken if token == "" { var err error token, err = v.authenticate(ctx, v.secret.Server) if err != nil { return nil, err } } if v.secret.Name == "" { return token, nil } return v.getSecret(ctx, token) } func (v *AkeylessAPI) authenticate(ctx context.Context, server common.AkeylessServer) (string, error) { authParams, err := setupAuthParams(server) if err != nil { return "", err } out, err := v.client.Auth(ctx, *authParams) if err != nil { return "", getAklApiErrMsg(err) } return out.GetToken(), nil } func (v *AkeylessAPI) getSecret(ctx context.Context, token string) (any, error) { name := v.secret.Name itemType, err := v.getItemType(ctx, name, token) if err != nil { return nil, err } var value any switch ItemType(itemType) { case ItemTypeStaticSecret: value, err = v.getStaticSecret(ctx, name, token) case ItemTypeDynamicSecret: value, err = v.getDynamicSecret(ctx, name, token) case ItemTypeRotatedSecret: value, err = v.getRotatedSecret(ctx, name, token) case ItemTypeSSHCertIssuer: value, err = v.getSSHCertificate(ctx, name, v.secret.CertUserName, v.secret.PublicKeyData, token) case ItemTypePkiCertIssuer: value, err = v.getPKICertificate(ctx, name, v.secret.CsrData, v.secret.PublicKeyData, token) default: return nil, fmt.Errorf("unknown item type: %s", itemType) } // since we return all kinds of values avoid wrapping the value in the `any` type, // so it's consistently nil if err != nil { return nil, err } return value, nil } func (v *AkeylessAPI) getStaticSecret(ctx context.Context, name, token string) (any, error) { secretsVal, err := v.client.GetSecretValue(ctx, akeyless_api.GetSecretValue{ Names: []string{name}, Token: akeyless_api.PtrString(token), }) if err != nil { return nil, err } if val, ok := secretsVal[name]; ok { return val, nil } return nil, getSecretNotFoundError(name) } func (v *AkeylessAPI) getDynamicSecret(ctx context.Context, name string, token string) (any, error) { return v.client.GetDynamicSecretValue(ctx, akeyless_api.GetDynamicSecretValue{ Name: name, Token: akeyless_api.PtrString(token), }) } func (v *AkeylessAPI) getRotatedSecret(ctx context.Context, name string, token string) (any, error) { resp, err := v.client.GetRotatedSecretValue(ctx, akeyless_api.GetRotatedSecretValue{ Names: name, Token: akeyless_api.PtrString(token), }) if err != nil { return nil, err } if val, ok := resp["value"]; ok { return val, nil } return nil, getSecretNotFoundError(name) } func (v *AkeylessAPI) getSSHCertificate(ctx context.Context, name, certUserName, publicKeyData, token string) (any, error) { resp, err := v.client.GetSSHCertificate(ctx, akeyless_api.GetSSHCertificate{ CertIssuerName: name, CertUsername: certUserName, PublicKeyData: akeyless_api.PtrString(publicKeyData), Token: akeyless_api.PtrString(token), }) if err != nil { return nil, err } if resp.Data != nil { return *resp.Data, nil } return nil, getSecretNotFoundError(name) } func (v *AkeylessAPI) getPKICertificate(ctx context.Context, name, csrData, publicKeyData, token string) (any, error) { resp, err := v.client.GetPKICertificate(ctx, akeyless_api.GetPKICertificate{ CertIssuerName: name, CsrDataBase64: akeyless_api.PtrString(csrData), KeyDataBase64: akeyless_api.PtrString(publicKeyData), Token: akeyless_api.PtrString(token), }) if err != nil { return nil, err } if resp.Data != nil { return *resp.Data, nil } return nil, getSecretNotFoundError(name) } func (v *AkeylessAPI) getItemType(ctx context.Context, name, token string) (string, error) { describeItemOut, err := v.client.DescribeItem(ctx, akeyless_api.DescribeItem{ Name: name, Token: akeyless_api.PtrString(token), }) if err != nil { return "", getAklApiErrMsg(err) } return describeItemOut.GetItemType(), nil } func setupAuthParams(server common.AkeylessServer) (*akeyless_api.Auth, error) { authParams := akeyless_api.NewAuth() authParams.SetAccessType(server.AkeylessAccessType) authParams.SetAccessId(server.AccessId) if server.GatewayCaCert != "" { authParams.SetCertData(server.GatewayCaCert) } switch AccessType(server.AkeylessAccessType) { case AccessTypeApiKey: authParams.SetAccessKey(server.AccessKey) case AccessTypeAwsIAM: id, err := aws.GetCloudId() if err != nil { return nil, fmt.Errorf("failed to get AWS cloud id: %w", err) } authParams.SetCloudId(id) case AccessTypeAzureAd: id, err := azure.GetCloudId(server.AzureObjectId) if err != nil { return nil, fmt.Errorf("failed to get azure cloud id: %w", err) } if _, err := base64.StdEncoding.DecodeString(id); err != nil { id = base64.StdEncoding.EncodeToString([]byte(id)) } authParams.SetCloudId(id) case AccessTypeGCP: id, err := gcp.GetCloudID(server.GcpAudience) if err != nil { return nil, fmt.Errorf("failed to get GCP cloud id: %w", err) } authParams.SetCloudId(id) case AccessTypeUid: if server.UidToken == "" { return nil, fmt.Errorf("UidToken is required for access type %q", AccessTypeUid) } authParams.SetUidToken(server.UidToken) case AccessTypeK8S: authParams.SetGatewayUrl(server.AkeylessApiUrl) authParams.SetK8sServiceAccountToken(server.K8SServiceAccountToken) authParams.SetK8sAuthConfigName(server.K8SAuthConfigName) case AccessTypeJWT: authParams.SetJwt(server.JWT) default: return nil, fmt.Errorf("unknown Access type: %s", server.AkeylessAccessType) } return authParams, nil } func getSecretNotFoundError(name string) error { return fmt.Errorf("secret %v not found", name) } func getAklApiErrMsg(err error) error { msg := "no response body" var apiErr akeyless_api.GenericOpenAPIError if errors.As(err, &apiErr) { msg = string(apiErr.Body()) } return fmt.Errorf("can't authenticate with static creds: %s: %w", msg, err) }