Setting up JFrog’s AssumeRole Capabilities in AWS

ARTIFACTORY: Setting up JFrog’s AssumeRole Capabilities in AWS

AuthorFullName__c
Yonatan Arbel, Batel Zohar
articleNumber
000005939
ft:sourceType
Salesforce
FirstPublishedDate
2023-12-14T19:28:08Z
lastModifiedDate
2023-12-14
VersionNumber
9
The allure of AssumeRole lies in its ability to enhance security without sacrificing operational efficiency. To illustrate this point, here is a comprehensive tutorial on setting up the JFrog Registry Operator to leverage AssumeRole’s capabilities in AWS. This configuration ensures seamless integration between Amazon EKS and the JFrog Platform.

Our objective is to create an EKS environment with Kubernetes deployed in a step-by-step fashion, featuring Nginx pulled from the JFrog Platform.

The initial step is selecting the namespace where the operator will be deployed
export NAMESPACE=jfrog-operator

Next, configure the service account name, which, in our case, is "jf-oper-sa"
export SERVICE_ACCOUNT_NAME=jf-oper-sa

The following step involves obtaining the OIDC Provider and Account ID:
oidc_provider=$(aws eks describe-cluster --name <Your EKS cluster name> --region <Your EKS region> --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")

Example:
oidc_provider=$(aws eks describe-cluster --name devrel-cluster --region eu-central-1 --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")

To verify our command, execute the following:
echo ${oidc_provider} 
Output: oidc.eks.eu-central-1.amazonaws.com/id/0C2D779F09A5788BXXXXXXXXX

Similarly, with the account ID:
account_id=$(aws sts get-caller-identity --query "Account" --output text)
Validate:
echo ${account_id}

Output: 710936890000 

The next step is creating the trust-relationship.json file. This file limits trust to a designated namespace and service account, ensuring that access to the relevant resources are given only as required.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<account id>:oidc-provider/<OIDC provider>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "<OIDC provider>:aud": "sts.amazonaws.com",
          "<OIDC provider>:sub": "system:serviceaccount:<namespace>:<service account>"
        }
      }
    }
  ]
}

Now the role can be applied:
aws iam create-role --role-name <ROLE_NAME> --assume-role-policy-document file://trust-relationship.json --description "my-role-description" --max-session-duration <TOKEN_EXPIRATION_SECONDS>

Example:
aws iam create-role --role-name jfrole --assume-role-policy-document file://trust-relationship.json --description "my-role-description" --max-session-duration 14400

Upon submission, the expected outcome should resemble the following:

User-added image


The next step is creating an IAM Policy for Data Access first using the my-policy.json file.

This policy, with the specified details, grants permission to retrieve the caller's identity using the sts:GetCallerIdentity action on the resource identified by the IAM role's ARN. Additionally, under "Statement1," it allows the specified IAM role's details to be retrieved using the iam:GetRole action on the corresponding IAM role's ARN


my-policy.json
{					
   "Version": "2012-10-17",
    "Statement": [
{					
         "Effect": "Allow",
        "Action": "sts:GetCallerIdentity",
        "Resource": "<ARN of the IAM role>"
},
 {
						
       "Sid": "Statement1",
        "Effect": "Allow",
        "Action": ["iam:GetRole"],
        "Resource": ["<ARN of the IAM role>"]					
} ]									
} 

In cases where the ARN of the role is unknown, run the following:
aws iam get-role --role-name <ROLE_NAME>

Example:
aws iam get-role --role-name jfrole 

Thel output includes the role details and ARN:
arn:aws:iam::<account_id>:role/<role_name>

Now create the policy by running the following command:
aws iam create-policy --policy-name <Desired-Policy-Name> --policy-document file://my-policy.json

Example:
aws iam create-policy --policy-name jf-policy --policy-document file://my-policy.json

Then attach the policy and the role using the provided command:
aws iam attach-role-policy --role-name <IAM role name> --policy-
arn=arn:aws:iam::$account_id:policy/<policy name>

Example:
aws iam attach-role-policy --role-name jfrole --policy-arn=arn:aws:iam::$account_id:policy/jf-policy

Optional step: By this point, it is important to validate that the updated roles and policies are reflected in the AWS console.

User-added image

User-added image

Now that the policy and role are attached to one another, it is necessary to Create OpenID Connect (OIDC) identity providers in AWS. 


Therefore in the IAM Console under Access Management --> Identity providers

Click on “Add provider”

Select the “OpenID Connect” option at the top

Enter the value of $oidc_provider as the Provider URL and click on “Get thumbprint” in the type sts.amazonaws.com  as in the following example:

User-added image


Now, navigate to the JFrog platform to complete the circle of trust by assigning a user tothe IAM role. This ensures that all related operations are done on behalf of that user

Here are the steps:
  1. Create a user with the desired permissions you intend to assign or select an existing user
  2. Generate an admin token based on  the existing or newly created user.

Once that is complete, run the following curl:
curl -XPUT -H "Content-type: application/json"  -H "Authorization: Bearer <Token>"  https://<Platform-URL>/access/api/v1/aws/iam_role -d '{"username":"<Username>", "iam_role": "arn:aws:iam::<Account-ID>:role/<Role-Name>"}' -vv

Example:
curl -XPUT -H "Content-type: application/json"  -H "Authorization: Bearer cmVmdGtuOjAxOjAwMDAwMDAwMDA6cGZlZklCZER3RFFDYVIzcktCVURoNmdwZFhR"  https://myexampleplatform.jfrog.io/access/api/v1/aws/iam_role -d '{"username":"aws-user", "iam_role": "arn:aws:iam::710936890000:role/jfrole"}' -vv

Now validate the operation by running this GET request :
curl -H "Content-type: application/json"  -H "Authorization: Bearer cmVmdGtuOjAxOjAwMDAwMDAwMDA6cGZlZklCZER3RFFDYVIzcktCVURoNmdwZFhR"  https://myexampleplatform.jfrog.io/access/api/v1/aws/iam_role/aws-user -vv

Response should include something like: 

{
  "username" : "aws-user",
  "iam_role" : "arn:aws:iam::710936890000:role/jfrole"
}

The final step involves installing the operator, enabling retrieval of Docker images directly from the JFrog platform without the need for managing secrets

Prior to executing the installer, it is essential to verify the presence of the following exported variables:
echo $SERVICE_ACCOUNT_NAME (if empty, recreate it with the same name you did at the beginning of the process)

Example:
export SERVICE_ACCOUNT_NAME=jf-oper-sa

Export the annotation, a mandatory step for the installation, using the following format:
export ANNOTATIONS="eks.amazonaws.com/role-arn: arn:aws:iam::<account_number>:role/<role_name>"

Example:
export ANNOTATIONS="eks.amazonaws.com/role-arn: arn:aws:iam::710936890000:role/jfrole"

Ensure location of the JFrog Helm repository by executing the command:
helm repo add jfrog https://charts.jfrog.io

And then run:
helm upgrade --install secretrotator jfrog/jfrog-registry-operator --set "serviceAccount.name=${SERVICE_ACCOUNT_NAME}" --set "serviceAccount.annotations=${ANNOTATIONS}" --create-namespace  -n ${NAMESPACE}

Upon successful completion, the following message should be displayed:
User-added image

The creation in our namespace is clear, per below:
User-added image

At this stage, the secret has not been created yet. A final action must still be taken to configure the secret rotator for its creation.

To accomplish this, create a secretrotator.yaml file with the pertinent information, such as the namespace and platform URL:
apiVersion: apps.jfrog.com/v1alpha1
kind: SecretRotator
metadata:
  labels:
    app.kubernetes.io/name: secretrotators.apps.jfrog.com
    app.kubernetes.io/instance: secretrotator
    app.kubernetes.io/created-by: artifactory-secrets-rotator
  name: secretrotator
spec:
  namespaceSelector:
    matchLabels:
      kubernetes.io/metadata.name: <target namespace>
  secretName: token-secret
  artifactoryUrl: <PLATFORM_URL>
  refreshTime: 30m
  secretMetadata:
    annotations:
      annotationKey: annotationValue
    labels:
      labelName: labelValue
Spec:
secretNamerequiredThe name of the kubernetes secret object  that will be created by the Registry Operator
namespaceSelector.matchLabelsrequiredIf you wish to apply this to all namespaces, utilize {} for the namespaceSelector, otherwise set the targeted namespaces, or any other label, through common selector setup
artifactoryUrlrequired
The base URL of the host from which Docker images will be retrieved, and where token exchange will take place. if downloads are done from edges, then access federation should be used, or manual setup of the user and certificate on all the relevant edges
refreshTimeoptionalan optional attribute, which overrides the default calculation of refresh time (default is 75% of role max session duration, 'roleMaxSession')

You can directly adjust the  role max session duration refresh time through the AWS console by navigating to IAM > Roles > [Created Role].
secretMetadata.labelsoptionalintended to be used to specify identifying attributes of objects that are meaningful and relevant to users
secretMetadata.anottationsoptionalfor adding arbitrary metadata to objects (such as pods, services, nodes, and more) that are not used for identification or selection purposes



Apply the configuration using the following command:
kubectl apply -f secretrotator.yaml -n ${NAMESPACE}

Upon completion, the secretrotator pod should display the following logs, indicating a successful operation:

User-added image

Execute the following command will confirm the creation of a secret:
k get secrets -n <NAMESPACE>

Output: 

NAME                                                   TYPE                                        DATA      AGE
sh.helm.release.v1.secretrotator.v1   helm.sh/release.v1                             1     4m56s
token-secret                                        kubernetes.io/dockerconfigjson         1      3m13s
Having completed these steps, you can now effortlessly retrieve images from your private Docker registry without the need to manage secrets. The integration of the JFrog Registry Operator and the secret rotation configuration streamlines the process and elevates the security of your containerized workflows on Amazon EKS.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: <target namesapce>
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: <artifactory_URL>/<DockerRepo-Name>/nginx:latest
        ports:
        - containerPort: 80
      imagePullSecrets:
      - name: token-secret