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)
}