Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ jobs:
draft: false
prerelease: true
files: |
build/bin/jfrog-credential-provider-aws-linux-amd64
build/bin/jfrog-credential-provider-aws-linux-arm64
build/bin/jfrog-credential-provider-linux-amd64
build/bin/jfrog-credential-provider-linux-arm64
fail_on_unmatched_files: true
12 changes: 6 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@ jobs:
- name: Sign Binaries
run: |
cd build/bin/
echo "${{ secrets.GPG_PASSPHRASE }}" | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --detach-sign --armor jfrog-credential-provider-aws-linux-amd64
echo "${{ secrets.GPG_PASSPHRASE }}" | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --detach-sign --armor jfrog-credential-provider-aws-linux-arm64
echo "${{ secrets.GPG_PASSPHRASE }}" | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --detach-sign --armor jfrog-credential-provider-linux-amd64
echo "${{ secrets.GPG_PASSPHRASE }}" | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --detach-sign --armor jfrog-credential-provider-linux-arm64

- name: Upload release artifacts
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.inputs.tag }}
generate_release_notes: true
files: |
build/bin/jfrog-credential-provider-aws-linux-amd64
build/bin/jfrog-credential-provider-aws-linux-arm64
build/bin/jfrog-credential-provider-aws-linux-amd64.asc
build/bin/jfrog-credential-provider-aws-linux-arm64.asc
build/bin/jfrog-credential-provider-linux-amd64
build/bin/jfrog-credential-provider-linux-arm64
build/bin/jfrog-credential-provider-linux-amd64.asc
build/bin/jfrog-credential-provider-linux-arm64.asc

104 changes: 95 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
JFROG_CREDENTIAL_PLUGIN_BINARY_URL:
description: 'BINARY_URL (CI adds arch suffix automatically)'
required: true
default: "https://releases.jfrog.io/artifactory/run/jfrog-credentials-provider/0.1.0-beta.1/jfrog-credential-provider-aws-linux"
default: "https://releases.jfrog.io/artifactory/run/jfrog-credentials-provider/0.1.0-beta.1/jfrog-credential-provider-linux"
type: string
DISABLE_TERRAFORM_DESTROY:
description: 'DISABLE_TERRAFORM_DESTROY'
Expand All @@ -22,7 +22,7 @@ env:
TF_VERSION: 1.5.7

jobs:
verify-kubelet-plugin:
verify-kubelet-plugin-aws:
runs-on: self-hosted
env:
ARTIFACTORY_TOKEN: ${{ secrets.ARTIFACTORY_TOKEN }}
Expand All @@ -42,6 +42,13 @@ jobs:
run: |
aws sts get-caller-identity

- name: Login to Azure with Federated Credentials
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_APP_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_APP_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_APP_SUBSCRIPTION_ID }}

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
Expand All @@ -50,22 +57,26 @@ jobs:

- name: Initialise Terraform
id: init
env:
AZURE_APP_SUBSCRIPTION_ID: ${{ secrets.AZURE_APP_SUBSCRIPTION_ID }}
run: |
echo "" >> build/terraform.tfvars
echo "jfrog_credential_provider_binary_url=\"$JFROG_CREDENTIAL_PLUGIN_BINARY_URL\"" >> build/terraform.tfvars
cp build/terraform.tfvars terraform-ci/terraform.tfvars
echo "" >> build/terraform.tfvars.aws
echo "jfrog_credential_provider_binary_url=\"$JFROG_CREDENTIAL_PLUGIN_BINARY_URL\"" >> build/terraform.tfvars.aws
# for azure, it is not possible to avoid azure authentication check, even when azure is disabled
echo "azure_subscription_id=\"$AZURE_APP_SUBSCRIPTION_ID\"" >> build/terraform.tfvars.aws
cp build/terraform.tfvars.aws terraform-ci/terraform.tfvars
cd terraform-ci
terraform init

- name: Run Terraform CI
- name: Run AWS Terraform CI
id: apply
run: |
cd terraform-ci
terraform apply -input=false -auto-approve
terraform output -json > terraform_output.json
echo "Terraform output: $(cat terraform_output.json)"

- name: Destroy terraform resources
- name: Destroy AWS terraform resources
id: destroy
if: always() && !env.DISABLE_TERRAFORM_DESTROY
continue-on-error: true
Expand All @@ -78,7 +89,82 @@ jobs:
if: always()
uses: actions/upload-artifact@v4
with:
name: terraform-context-for-manual-cleanup
name: terraform-context-for-manual-cleanup-aws
path: |
terraform-ci/**/*.tf
terraform-ci/jfrog/*
terraform-ci/terraform.tfstate
terraform-ci/terraform.tfstate.backup
terraform-ci/terraform.tfvars
terraform-ci/.terraform.lock.hcl
terraform-ci/terraform_output.json
retention-days: 1

verify-kubelet-plugin-azure:
runs-on: self-hosted
env:
ARTIFACTORY_TOKEN: ${{ secrets.ARTIFACTORY_TOKEN }}
JFROG_CREDENTIAL_PLUGIN_BINARY_URL: ${{ github.event.inputs.JFROG_CREDENTIAL_PLUGIN_BINARY_URL }}
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Install Azure CLI
uses: pietrobolcato/install-azure-cli-action@main

- name: Login to Azure with Federated Credentials
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_APP_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_APP_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_APP_SUBSCRIPTION_ID }}

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.TF_VERSION }}
terraform_wrapper: false

- name: Initialise Terraform
id: init
env:
AZURE_APP_SUBSCRIPTION_ID: ${{ secrets.AZURE_APP_SUBSCRIPTION_ID }}
run: |
echo "" >> build/terraform.tfvars.azure
echo "jfrog_credential_provider_binary_url=\"$JFROG_CREDENTIAL_PLUGIN_BINARY_URL\"" >> build/terraform.tfvars.azure
echo "azure_subscription_id=\"$AZURE_APP_SUBSCRIPTION_ID\"" >> build/terraform.tfvars.azure
cp build/terraform.tfvars.azure terraform-ci/terraform.tfvars
cd terraform-ci
terraform init

- name: Run Azure Terraform CI
id: apply
run: |
# to avoid credentials check for aws
cd terraform-ci
cat terraform.tfvars
terraform apply -input=false -auto-approve
terraform output -json > terraform_output.json
echo "Terraform output: $(cat terraform_output.json)"

- name: Destroy Azure terraform resources
id: destroy
if: always() && !env.DISABLE_TERRAFORM_DESTROY
uses: nick-fields/retry@v3
with:
timeout_minutes: 20
max_attempts: 3
continue-on-error: true
run: |
cd terraform-ci
terraform destroy -input=false -auto-approve
rm terraform.tfstate terraform.tfstate.backup terraform_output.json

- name: Upload Terraform context for manual cleanup
if: always()
uses: actions/upload-artifact@v4
with:
name: terraform-context-for-manual-cleanup-azure
path: |
terraform-ci/**/*.tf
terraform-ci/jfrog/*
Expand All @@ -87,4 +173,4 @@ jobs:
terraform-ci/terraform.tfvars
terraform-ci/.terraform.lock.hcl
terraform-ci/terraform_output.json
retention-days: 1
retention-days: 1
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ This project is currently in its beta phase, meaning it's still under active dev

# JFrog Kubelet Credential Provider

A [Kubernetes kubelet credential provider](https://kubernetes.io/docs/tasks/administer-cluster/kubelet-credential-provider/) **for Amazon EKS** that enables seamless authentication with JFrog Artifactory for container image pulls in Amazon EKS, eliminating the need for manual image pull secret management.
A [Kubernetes kubelet credential provider](https://kubernetes.io/docs/tasks/administer-cluster/kubelet-credential-provider/) **for Amazon EKS and Azure AKS** that enables seamless authentication with JFrog Artifactory for container image pulls, eliminating the need for manual image pull secret management.

> **Coming Soon**: Azure AKS and Google Cloud GKE support are currently in development.
> **Coming Soon**: Google Cloud GKE support is currently in development.

## Overview

Expand All @@ -22,7 +22,7 @@ The JFrog Kubelet Credential Provider leverages the native Kubernetes kubelet Cr
1. A pod is created with an image stored in JFrog Artifactory
2. Kubelet identifies the image URL matches the configured pattern for the JFrog Kubelet Credential Provider
3. Kubelet invokes the JFrog Kubelet Credential Provider binary
4. The provider authenticates with AWS (using IAM roles or OIDC) and exchanges credentials with Artifactory
4. The provider authenticates with the cloud provider (AWS IAM roles/OIDC or Azure managed identities) and exchanges credentials with Artifactory
5. Valid registry credentials are returned to kubelet for the image pull

## Quick Start
Expand Down Expand Up @@ -51,18 +51,26 @@ See the [terraform-module](./terraform-module) directory for detailed deployment

## Authentication Methods

### AWS Authentication
- **AWS IAM Role Assumption**: Uses EC2 instance [IAM roles](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html) for authentication
- **AWS Cognito OIDC**: Uses OIDC tokens from [AWS Cognito](https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html) for authentication

**Note**: You must select either IAM Role Assumption OR Cognito OIDC as your authentication method. They cannot be used simultaneously in the same deployment.
**Note**: For AWS, You must select either IAM Role Assumption OR Cognito OIDC as your authentication method.
They cannot be used simultaneously in the same deployment.

### Azure Authentication
- **Azure Managed Identity OIDC**: Uses [Azure managed identities](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) with OIDC for authentication


## Requirements

- Amazon EKS cluster
- Amazon EKS cluster or Azure AKS cluster
- JFrog Artifactory instance
- Based on your chosen authentication method:
- **For IAM Role Assumption**: IAM role mapped to a JFrog Artifactory user
- **For Cognito OIDC**: OIDC provider and identity mappings. For more information, see [terraform-module](./terraform-module)
- Based on your chosen cloud provider and authentication method:
- **For AWS IAM Role Assumption**: IAM role mapped to a JFrog Artifactory user
- **For AWS Cognito OIDC**: OIDC provider and identity mappings
- **For Azure Managed Identity**: Azure AD application for OIDC and kubelet Identity.
For more information, see [terraform-module](./terraform-module)


## Logging and Debugging
Expand Down
5 changes: 4 additions & 1 deletion build/terraform.tfvars → build/terraform.tfvars.aws
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ self_managed_eks_cluster = {
name = "aws-operator-jfrog"
}

jfrog_namespace = "jfrog"
jfrog_namespace = "jfrog"

enable_aws = true

32 changes: 32 additions & 0 deletions build/terraform.tfvars.azure
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# The JFrog Credential Provider binary URL (no authentication required)
# added by CI
# jfrog_credential_provider_binary_url = "https://releases.jfrog.io/artifactory/run/jfrog-credentials-provider/0.1.0-beta.1/jfrog-credential-provider-aws-linux"

# The JFrog Artifactory URL (the one that will be the EKS container registry)
artifactory_url = "partnership.jfrog.io"
# Change this to jfrogurl


# The JFrog Artifactory username that will be granted the assume role permission
artifactory_user = "aws-eks-user"

jfrog_namespace = "jfrog"

enable_aws = false

enable_azure = true

azure_resource_group_name = "infra-robin-test"

azure_location = "eastus"

create_aks_cluster = true

aks_cluster_name = "jfrog-test-aks"

azure_node_count = 3
azure_node_vm_size = "Standard_D2pds_v5"
azure_admin_username = "jfrog"

# time to live is very short for testing
azure_cluster_public_access_cidrs = ["0.0.0.0/0"]
8 changes: 4 additions & 4 deletions internal/autoupdate/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ func fetchLatestVersionTag(ctx context.Context, client *http.Client, currentVers
}

var latestVersionTag = addVPrefix(logs, currentVersion)
if !semver.IsValid(currentVersion) {
logs.Error("Current Version" + currentVersion + "isn't valid! Exiting")
return "", fmt.Errorf("invalid current version: %s", currentVersion)
if !semver.IsValid(latestVersionTag) {
logs.Error("Current Version " + latestVersionTag + " isn't valid! Exiting")
return "", fmt.Errorf("invalid current version: %s", latestVersionTag)
}

logs.Info("Current version: " + latestVersionTag)
Expand Down Expand Up @@ -184,7 +184,7 @@ func downloadLatestBinary(ctx context.Context, logs *logger.Logger, client *http
logs.Info("Release tag '%s' is missing 'v' prefix. Prepending 'v'." + newVersion)
newVersion = strings.TrimPrefix(newVersion, "v")
}
downloadUrl := jfrogPluginDownloadUrl + downloadSuffix + newVersion + "/jfrog-credential-provider-aws-linux-" + getArchSuffix(logs)
downloadUrl := jfrogPluginDownloadUrl + downloadSuffix + newVersion + "/jfrog-credential-provider-linux-" + getArchSuffix(logs)
logs.Info("Downloading new binary from: " + downloadUrl)
downloadSignUrl := downloadUrl + ".asc"
err := downloadReleaseArtifacts(ctx, logs, client, newBinaryPath, downloadUrl)
Expand Down
18 changes: 15 additions & 3 deletions internal/autoupdate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"os"
"os/exec"
"strings"

"gopkg.in/yaml.v3"
)

type EnvVar struct {
Expand Down Expand Up @@ -55,9 +57,19 @@ func loadProviderEnvsFromFile(logs *logger.Logger, configPath string, targetProv
}

var config CredentialProviderConfig
if err := json.Unmarshal(data, &config); err != nil {
logs.Error("Error unmarshalling config JSON: " + err.Error())
return nil, err

// if yaml, parse it in yaml
if strings.HasSuffix(configPath, ".yaml") || strings.HasSuffix(configPath, ".yml") {
if err := yaml.Unmarshal(data, &config); err != nil {
logs.Error("Error unmarshalling config YAML: " + err.Error())
return nil, err
}
} else {
// if json, parse it in json
if err := json.Unmarshal(data, &config); err != nil {
logs.Error("Error unmarshalling config JSON: " + err.Error())
return nil, err
}
}

var providerEnvs []string
Expand Down
Loading