| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- /*
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package akeyless
- import (
- "context"
- "encoding/base64"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "os"
- "strings"
- aws_cloud_id "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/aws"
- azure_cloud_id "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/azure"
- gcp_cloud_id "github.com/akeylesslabs/akeyless-go-cloud-id/cloudprovider/gcp"
- "github.com/akeylesslabs/akeyless-go/v3"
- authenticationv1 "k8s.io/api/authentication/v1"
- corev1 "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/types"
- esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
- esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
- "github.com/external-secrets/external-secrets/pkg/constants"
- "github.com/external-secrets/external-secrets/pkg/metrics"
- "github.com/external-secrets/external-secrets/pkg/utils/resolvers"
- )
- var apiErr akeyless.GenericOpenAPIError
- const DefServiceAccountFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
- func (a *akeylessBase) GetToken(accessID, accType, accTypeParam string, k8sAuth *esv1beta1.AkeylessKubernetesAuth) (string, error) {
- ctx := context.Background()
- authBody := akeyless.NewAuthWithDefaults()
- authBody.AccessId = akeyless.PtrString(accessID)
- if accType == "api_key" || accType == "access_key" {
- authBody.AccessKey = akeyless.PtrString(accTypeParam)
- } else if accType == "k8s" {
- jwtString, err := a.getK8SServiceAccountJWT(ctx, k8sAuth)
- if err != nil {
- return "", fmt.Errorf("failed to read JWT with Kubernetes Auth from %v. error: %w", DefServiceAccountFile, err)
- }
- jwtStringBase64 := base64.StdEncoding.EncodeToString([]byte(jwtString))
- K8SAuthConfigName := accTypeParam
- authBody.AccessType = akeyless.PtrString(accType)
- authBody.K8sServiceAccountToken = akeyless.PtrString(jwtStringBase64)
- authBody.K8sAuthConfigName = akeyless.PtrString(K8SAuthConfigName)
- } else {
- cloudID, err := a.getCloudID(accType, accTypeParam)
- if err != nil {
- return "", errors.New("Require Cloud ID " + err.Error())
- }
- authBody.AccessType = akeyless.PtrString(accType)
- authBody.CloudId = akeyless.PtrString(cloudID)
- }
- authOut, res, err := a.RestAPI.Auth(ctx).Body(*authBody).Execute()
- metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMAuth, err)
- if err != nil {
- if errors.As(err, &apiErr) {
- return "", fmt.Errorf("authentication failed: %v", string(apiErr.Body()))
- }
- return "", fmt.Errorf("authentication failed: %w", err)
- }
- defer res.Body.Close()
- token := authOut.GetToken()
- return token, nil
- }
- func (a *akeylessBase) GetSecretByType(ctx context.Context, secretName, token string, version int32) (string, error) {
- item, err := a.DescribeItem(ctx, secretName, token)
- if err != nil {
- return "", err
- }
- secretType := item.GetItemType()
- switch secretType {
- case "STATIC_SECRET":
- return a.GetStaticSecret(ctx, secretName, token, version)
- case "DYNAMIC_SECRET":
- return a.GetDynamicSecrets(ctx, secretName, token)
- case "ROTATED_SECRET":
- return a.GetRotatedSecrets(ctx, secretName, token, version)
- case "CERTIFICATE":
- return a.GetCertificate(ctx, secretName, token, version)
- default:
- return "", fmt.Errorf("invalid item type: %v", secretType)
- }
- }
- func (a *akeylessBase) DescribeItem(ctx context.Context, itemName, token string) (*akeyless.Item, error) {
- body := akeyless.DescribeItem{
- Name: itemName,
- }
- if strings.HasPrefix(token, "u-") {
- body.UidToken = &token
- } else {
- body.Token = &token
- }
- gsvOut, res, err := a.RestAPI.DescribeItem(ctx).Body(body).Execute()
- metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMDescribeItem, err)
- if err != nil {
- if errors.As(err, &apiErr) {
- var item *Item
- err = json.Unmarshal(apiErr.Body(), &item)
- if err != nil {
- return nil, fmt.Errorf("can't describe item: %v, error: %v", itemName, string(apiErr.Body()))
- }
- } else {
- return nil, fmt.Errorf("can't describe item: %w", err)
- }
- }
- defer res.Body.Close()
- return &gsvOut, nil
- }
- func (a *akeylessBase) GetCertificate(ctx context.Context, certificateName, token string, version int32) (string, error) {
- body := akeyless.GetCertificateValue{
- Name: certificateName,
- Version: &version,
- }
- if strings.HasPrefix(token, "u-") {
- body.UidToken = &token
- } else {
- body.Token = &token
- }
- gcvOut, res, err := a.RestAPI.GetCertificateValue(ctx).Body(body).Execute()
- metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetCertificateValue, err)
- if err != nil {
- if errors.As(err, &apiErr) {
- return "", fmt.Errorf("can't get certificate value: %v", string(apiErr.Body()))
- }
- return "", fmt.Errorf("can't get certificate value: %w", err)
- }
- defer res.Body.Close()
- out, err := json.Marshal(gcvOut)
- if err != nil {
- return "", fmt.Errorf("can't marshal certificate value: %w", err)
- }
- return string(out), nil
- }
- func (a *akeylessBase) GetRotatedSecrets(ctx context.Context, secretName, token string, version int32) (string, error) {
- body := akeyless.GetRotatedSecretValue{
- Names: secretName,
- Version: &version,
- }
- if strings.HasPrefix(token, "u-") {
- body.UidToken = &token
- } else {
- body.Token = &token
- }
- gsvOut, res, err := a.RestAPI.GetRotatedSecretValue(ctx).Body(body).Execute()
- metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetRotatedSecretValue, err)
- if err != nil {
- if errors.As(err, &apiErr) {
- return "", fmt.Errorf("can't get rotated secret value: %v", string(apiErr.Body()))
- }
- return "", fmt.Errorf("can't get rotated secret value: %w", err)
- }
- defer res.Body.Close()
- valI, ok := gsvOut["value"]
- if ok {
- val, convert := valI.(map[string]interface{})
- if !convert {
- return "", fmt.Errorf("failure converting key from gsvOut")
- }
- if _, ok := val["payload"]; ok {
- return fmt.Sprintf("%v", val["payload"]), nil
- } else if _, ok := val["target_value"]; ok {
- out, err := json.Marshal(val["target_value"])
- if err != nil {
- return "", fmt.Errorf("can't marshal rotated secret value: %w", err)
- }
- return string(out), nil
- } else {
- out, err := json.Marshal(val)
- if err != nil {
- return "", fmt.Errorf("can't marshal rotated secret value: %w", err)
- }
- return string(out), nil
- }
- }
- out, err := json.Marshal(gsvOut)
- if err != nil {
- return "", fmt.Errorf("can't marshal rotated secret value: %w", err)
- }
- return string(out), nil
- }
- func (a *akeylessBase) GetDynamicSecrets(ctx context.Context, secretName, token string) (string, error) {
- body := akeyless.GetDynamicSecretValue{
- Name: secretName,
- }
- if strings.HasPrefix(token, "u-") {
- body.UidToken = &token
- } else {
- body.Token = &token
- }
- gsvOut, res, err := a.RestAPI.GetDynamicSecretValue(ctx).Body(body).Execute()
- metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetDynamicSecretValue, err)
- if err != nil {
- if errors.As(err, &apiErr) {
- return "", fmt.Errorf("can't get dynamic secret value: %v", string(apiErr.Body()))
- }
- return "", fmt.Errorf("can't get dynamic secret value: %w", err)
- }
- defer res.Body.Close()
- out, err := json.Marshal(gsvOut)
- if err != nil {
- return "", fmt.Errorf("can't marshal dynamic secret value: %w", err)
- }
- return string(out), nil
- }
- func (a *akeylessBase) GetStaticSecret(ctx context.Context, secretName, token string, version int32) (string, error) {
- gsvBody := akeyless.GetSecretValue{
- Names: []string{secretName},
- Version: &version,
- }
- if strings.HasPrefix(token, "u-") {
- gsvBody.UidToken = &token
- } else {
- gsvBody.Token = &token
- }
- gsvOut, res, err := a.RestAPI.GetSecretValue(ctx).Body(gsvBody).Execute()
- metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMGetSecretValue, err)
- if err != nil {
- if errors.As(err, &apiErr) {
- return "", fmt.Errorf("can't get secret value: %v", string(apiErr.Body()))
- }
- return "", fmt.Errorf("can't get secret value: %w", err)
- }
- defer res.Body.Close()
- val, ok := gsvOut[secretName]
- if !ok {
- return "", fmt.Errorf("can't get secret: %v", secretName)
- }
- return val, nil
- }
- func (a *akeylessBase) getCloudID(provider, accTypeParam string) (string, error) {
- var cloudID string
- var err error
- switch provider {
- case "azure_ad":
- cloudID, err = azure_cloud_id.GetCloudId(accTypeParam)
- case "aws_iam":
- cloudID, err = aws_cloud_id.GetCloudId()
- case "gcp":
- cloudID, err = gcp_cloud_id.GetCloudID(accTypeParam)
- default:
- return "", fmt.Errorf("unable to determine provider: %s", provider)
- }
- return cloudID, err
- }
- func (a *akeylessBase) ListSecrets(ctx context.Context, path, tag, token string) ([]string, error) {
- secretTypes := &[]string{"static-secret", "dynamic-secret", "rotated-secret"}
- MinimalView := true
- if tag != "" {
- MinimalView = false
- }
- gsvBody := akeyless.ListItems{
- Filter: &path,
- Type: secretTypes,
- MinimalView: &MinimalView,
- Tag: &tag,
- }
- if strings.HasPrefix(token, "u-") {
- gsvBody.UidToken = &token
- } else {
- gsvBody.Token = &token
- }
- lipOut, res, err := a.RestAPI.ListItems(ctx).Body(gsvBody).Execute()
- metrics.ObserveAPICall(constants.ProviderAKEYLESSSM, constants.CallAKEYLESSSMListItems, err)
- if err != nil {
- if errors.As(err, &apiErr) {
- return nil, fmt.Errorf("can't get secrets list: %v", string(apiErr.Body()))
- }
- return nil, fmt.Errorf("error on get secrets list: %w", err)
- }
- defer res.Body.Close()
- if lipOut.Items == nil {
- return nil, nil
- }
- listNames := make([]string, 0)
- for _, v := range *lipOut.Items {
- if path == "" || strings.HasPrefix(*v.ItemName, path) {
- listNames = append(listNames, *v.ItemName)
- }
- }
- return listNames, nil
- }
- func (a *akeylessBase) getK8SServiceAccountJWT(ctx context.Context, kubernetesAuth *esv1beta1.AkeylessKubernetesAuth) (string, error) {
- if kubernetesAuth != nil {
- if kubernetesAuth.ServiceAccountRef != nil {
- // Kubernetes <v1.24 fetch token via ServiceAccount.Secrets[]
- jwt, err := a.getJWTFromServiceAccount(ctx, kubernetesAuth.ServiceAccountRef)
- if jwt != "" {
- return jwt, err
- }
- // Kubernetes >=v1.24: fetch token via TokenRequest API
- jwt, err = a.getJWTfromServiceAccountToken(ctx, *kubernetesAuth.ServiceAccountRef, nil, 600)
- if err != nil {
- return "", err
- }
- return jwt, nil
- } else if kubernetesAuth.SecretRef != nil {
- tokenRef := kubernetesAuth.SecretRef
- if tokenRef.Key == "" {
- tokenRef = kubernetesAuth.SecretRef.DeepCopy()
- tokenRef.Key = "token"
- }
- jwt, err := resolvers.SecretKeyRef(ctx, a.kube, a.storeKind, a.namespace, tokenRef)
- if err != nil {
- return "", err
- }
- return jwt, nil
- }
- }
- return readK8SServiceAccountJWT()
- }
- func (a *akeylessBase) getJWTFromServiceAccount(ctx context.Context, serviceAccountRef *esmeta.ServiceAccountSelector) (string, error) {
- serviceAccount := &corev1.ServiceAccount{}
- ref := types.NamespacedName{
- Namespace: a.namespace,
- Name: serviceAccountRef.Name,
- }
- if (a.storeKind == esv1beta1.ClusterSecretStoreKind) &&
- (serviceAccountRef.Namespace != nil) {
- ref.Namespace = *serviceAccountRef.Namespace
- }
- err := a.kube.Get(ctx, ref, serviceAccount)
- if err != nil {
- return "", fmt.Errorf(errGetKubeSA, ref.Name, err)
- }
- if len(serviceAccount.Secrets) == 0 {
- return "", fmt.Errorf(errGetKubeSASecrets, ref.Name)
- }
- for _, tokenRef := range serviceAccount.Secrets {
- token, err := resolvers.SecretKeyRef(ctx, a.kube, a.storeKind, a.namespace, &esmeta.SecretKeySelector{
- Name: tokenRef.Name,
- Namespace: &ref.Namespace,
- Key: "token",
- })
- if err != nil {
- continue
- }
- return token, nil
- }
- return "", fmt.Errorf(errGetKubeSANoToken, ref.Name)
- }
- func (a *akeylessBase) getJWTfromServiceAccountToken(ctx context.Context, serviceAccountRef esmeta.ServiceAccountSelector, additionalAud []string, expirationSeconds int64) (string, error) {
- audiences := serviceAccountRef.Audiences
- if len(additionalAud) > 0 {
- audiences = append(audiences, additionalAud...)
- }
- tokenRequest := &authenticationv1.TokenRequest{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: a.namespace,
- },
- Spec: authenticationv1.TokenRequestSpec{
- Audiences: audiences,
- ExpirationSeconds: &expirationSeconds,
- },
- }
- if (a.storeKind == esv1beta1.ClusterSecretStoreKind) &&
- (serviceAccountRef.Namespace != nil) {
- tokenRequest.Namespace = *serviceAccountRef.Namespace
- }
- tokenResponse, err := a.corev1.ServiceAccounts(tokenRequest.Namespace).CreateToken(ctx, serviceAccountRef.Name, tokenRequest, metav1.CreateOptions{})
- if err != nil {
- return "", fmt.Errorf(errGetKubeSATokenRequest, serviceAccountRef.Name, err)
- }
- return tokenResponse.Status.Token, nil
- }
- // readK8SServiceAccountJWT reads the JWT data for the Agent to submit to Akeyless Gateway.
- func readK8SServiceAccountJWT() (string, error) {
- data, err := os.Open(DefServiceAccountFile)
- if err != nil {
- return "", err
- }
- defer data.Close()
- contentBytes, err := io.ReadAll(data)
- if err != nil {
- return "", err
- }
- jwt := strings.TrimSpace(string(contentBytes))
- return jwt, nil
- }
|