CodeArtifact + GitHub Actions
Pattern for secure private package management using AWS CodeArtifact with OIDC authentication.
Problem
Teams need to securely consume private packages and publish internal libraries from GitHub Actions workflows without managing AWS credentials as secrets, while following least-privilege principles and maintaining separation between read and publish operations.
Context
When to Use This Pattern
- Publishing internal npm or Python packages from CI/CD pipelines
- Installing private dependencies in application builds
- Implementing package registries with upstream caching to public registries (npmjs, PyPI)
- Requiring audit trails for package operations
- Working with GitHub-hosted runners in organizational repositories
When NOT to Use This Pattern
- Simple projects with only public dependencies (use public registries directly)
- Self-hosted runners with IAM instance profiles (use instance profiles instead)
- Projects requiring Maven/NuGet (adapt the pattern for those package managers)
- Local development environments (use AWS CLI
aws codeartifact loginwith personal credentials)
Solution
Use GitHub OIDC (OpenID Connect) to authenticate GitHub Actions workflows to AWS without storing long-lived credentials. Configure separate IAM roles for read-only (CI builds) and publish (releases) operations, with trust policies that restrict access based on repository, branch, or tag patterns.
Core Components
- CodeArtifact Infrastructure: Domain and repositories managed in the central
infrarepository - IAM Policies: Separate read and write policies with least-privilege permissions
- IAM Roles: OIDC-based roles with trust policies restricting GitHub repository access
- GitHub Workflows: Thin wrappers around Taskfile tasks that assume AWS roles via OIDC
- Taskfile Tasks: Reusable package operations (install, publish) that can run locally and in CI
Structure
Infrastructure Layout
infra/
└── global/
└── codeartifact/
├── main.tf # Domain and repositories
├── iam.tf # Policies and roles
├── outputs.tf # Outputs for consumption
└── variables.tf # Configuration variables
Current Ontopix Configuration
Based on infra/global/codeartifact/:
- Domain:
ontopix - Region:
eu-central-1 - Repositories:
npm(internal) withnpm-upstream(proxy to public npm)pypi(internal) withpypi-upstream(proxy to public PyPI)
- IAM Policies:
CodeArtifactReadAccess: Read-only operationsCodeArtifactWriteAccess: Read and publish operations
Implementation
Step 1: Configure GitHub OIDC Provider (Infrastructure)
The current infrastructure needs to be updated to support GitHub Actions OIDC. This requires:
1.1: Create OIDC Provider (if not exists)
# infra/global/iam/github_oidc.tf
resource "aws_iam_openid_connect_provider" "github_actions" {
url = "https://token.actions.githubusercontent.com"
client_id_list = [
"sts.amazonaws.com"
]
thumbprint_list = [
"6938fd4d98bab03faadb97b34396831e3780aea1" # GitHub OIDC thumbprint
]
tags = {
Name = "GitHubActionsOIDC"
Environment = "global"
ManagedBy = "terraform"
}
}
1.2: Update CodeArtifact IAM Roles with OIDC Trust Policy
Replace the current CodeArtifactCICDRole with two separate roles:
# infra/global/codeartifact/iam.tf
# Read-only role for CI (tests, builds, PR checks)
resource "aws_iam_role" "codeartifact_github_read" {
name = "GitHubActions-CodeArtifact-ReadRole"
description = "Read-only access to CodeArtifact for GitHub Actions CI workflows"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Federated = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
# Allow any repository in the ontopix organization
"token.actions.githubusercontent.com:sub" = "repo:ontopix/*:*"
}
}
}
]
})
tags = {
Name = "GitHubActions-CodeArtifact-ReadRole"
Environment = "global"
ManagedBy = "terraform"
}
}
resource "aws_iam_role_policy_attachment" "github_read_access" {
role = aws_iam_role.codeartifact_github_read.name
policy_arn = aws_iam_policy.codeartifact_read.arn
}
# Publish role for releases (restricted to tags)
resource "aws_iam_role" "codeartifact_github_publish" {
name = "GitHubActions-CodeArtifact-PublishRole"
description = "Read and publish access to CodeArtifact for GitHub Actions release workflows"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Federated = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
# Only allow releases from version tags
"token.actions.githubusercontent.com:sub" = "repo:ontopix/*:ref:refs/tags/v*"
}
}
}
]
})
tags = {
Name = "GitHubActions-CodeArtifact-PublishRole"
Environment = "global"
ManagedBy = "terraform"
}
}
resource "aws_iam_role_policy_attachment" "github_publish_access" {
role = aws_iam_role.codeartifact_github_publish.name
policy_arn = aws_iam_policy.codeartifact_write.arn
}
1.3: Add Outputs for GitHub Actions
# infra/global/codeartifact/outputs.tf (add these)
output "github_read_role_arn" {
description = "IAM role ARN for GitHub Actions read access"
value = aws_iam_role.codeartifact_github_read.arn
}
output "github_publish_role_arn" {
description = "IAM role ARN for GitHub Actions publish access"
value = aws_iam_role.codeartifact_github_publish.arn
}
1.4: Apply Infrastructure Changes
cd ../infra
task infra:plan # Review changes
# After review and approval
task infra:apply # Apply changes
Step 2: Configure Repository Taskfile
Add CodeArtifact tasks to your repository's Taskfile.yaml:
# Taskfile.yaml
version: '3'
vars:
CODEARTIFACT_DOMAIN: ontopix
CODEARTIFACT_REGION: eu-central-1
AWS_ACCOUNT_ID:
sh: aws sts get-caller-identity --query Account --output text 2>/dev/null || echo "unknown"
tasks:
deps:install:
desc: Install dependencies from CodeArtifact
cmds:
- task: deps:login
- npm ci # or pip install -r requirements.txt
deps:login:
desc: Login to CodeArtifact (requires AWS credentials)
cmds:
- |
aws codeartifact login \
--tool npm \
--domain {{.CODEARTIFACT_DOMAIN}} \
--domain-owner {{.AWS_ACCOUNT_ID}} \
--repository npm \
--region {{.CODEARTIFACT_REGION}}
preconditions:
- sh: command -v aws
msg: "AWS CLI is required"
deps:login:pypi:
desc: Login to CodeArtifact for Python (requires AWS credentials)
cmds:
- |
aws codeartifact login \
--tool pip \
--domain {{.CODEARTIFACT_DOMAIN}} \
--domain-owner {{.AWS_ACCOUNT_ID}} \
--repository pypi \
--region {{.CODEARTIFACT_REGION}}
preconditions:
- sh: command -v aws
msg: "AWS CLI is required"
release:publish:
desc: Publish package to CodeArtifact
cmds:
- task: deps:login
- npm publish # or python -m build && twine upload
preconditions:
- sh: test -f package.json # or setup.py/pyproject.toml
msg: "Package configuration not found"
Step 3: Create GitHub Actions Workflows
3.1: CI Workflow (Read-only access)
# .github/workflows/ci.yaml
name: CI
on:
push:
branches: [master, main]
pull_request:
branches: [master, main]
# OIDC permissions required
permissions:
id-token: write
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Configure AWS credentials via OIDC
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GitHubActions-CodeArtifact-ReadRole
aws-region: eu-central-1
# Setup Task
- name: Setup Task
uses: arduino/setup-task@v2
# Setup language environment
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# Use Taskfile for operations
- name: Install Dependencies
run: task deps:install
- name: Run Tests
run: task test:all
- name: Run Linter
run: task lint:check
3.2: Release Workflow (Publish access)
# .github/workflows/release.yaml
name: Release
on:
push:
tags:
- 'v*'
# OIDC permissions required
permissions:
id-token: write
contents: read
jobs:
publish:
runs-on: ubuntu-latest
# Optional: Use GitHub Environments for additional approval gates
environment: production
steps:
- uses: actions/checkout@v4
# Configure AWS credentials via OIDC with publish role
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GitHubActions-CodeArtifact-PublishRole
aws-region: eu-central-1
# Setup Task
- name: Setup Task
uses: arduino/setup-task@v2
# Setup language environment
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# Install, build, and publish via Taskfile
- name: Install Dependencies
run: task deps:install
- name: Build Package
run: task build
- name: Publish to CodeArtifact
run: task release:publish
Step 4: Configure GitHub Repository Secrets
Add the AWS account ID as a GitHub repository secret:
- Go to repository Settings → Secrets and variables → Actions
- Add secret:
- Name:
AWS_ACCOUNT_ID - Value: Your AWS account ID (from
infra/global/outputs.tf)
- Name:
Note: This is the ONLY secret required. No AWS access keys needed.
Step 5: Local Development Setup
For local development, developers should authenticate using their personal AWS credentials:
# Configure AWS CLI with personal credentials (one-time setup)
aws configure --profile ontopix
# Login to CodeArtifact
task deps:login
# Install dependencies
task deps:install
# Work normally
task test:all
Applies Principles
- Security by Design: OIDC removes long-lived credentials, reducing attack surface
- Least Privilege: Separate roles for read/publish with minimal required permissions
- Reproducibility: Same Taskfile tasks work locally and in CI
- Separation of Concerns: GitHub Actions handles triggers/environment, Taskfile handles operations
- Vendor Neutrality: Logic in Taskfile is portable across CI systems
- Audit Trail: CloudTrail logs all CodeArtifact operations with GitHub actor identity
Consequences
Benefits
✅ No Credentials in GitHub Secrets: OIDC eliminates long-lived AWS access keys ✅ Automatic Credential Rotation: OIDC tokens expire after use ✅ Fine-Grained Access Control: Trust policies can restrict by repo, branch, tag, or environment ✅ Audit Trail: CloudTrail captures GitHub user and workflow information ✅ Local/CI Parity: Same Taskfile tasks work in both environments ✅ Upstream Caching: Internal repositories cache public packages, improving build reliability ✅ Cost Efficiency: Pay only for private package storage, not bandwidth for cached public packages
Trade-offs
⚠️ Infrastructure Dependency: Requires OIDC provider and IAM role configuration ⚠️ Initial Setup Complexity: More complex than simple access keys (but more secure) ⚠️ GitHub-Specific: OIDC trust policy is tied to GitHub Actions (portable to other OIDC providers with adjustments) ⚠️ Region Dependency: CodeArtifact is regional (Ontopix uses eu-central-1) ⚠️ Learning Curve: Team needs to understand OIDC authentication flow
Limitations
❌ Self-Hosted Runners: If using self-hosted runners, consider instance profiles instead ❌ Cross-Account Access: Pattern needs adjustment for multi-account setups ❌ Non-GitHub CI: Different OIDC configuration required for GitLab, CircleCI, etc.
Examples
Example: npm Package Publishing
# package.json
{
"name": "@ontopix/my-package",
"version": "1.0.0",
"publishConfig": {
"registry": "https://ontopix-${AWS_ACCOUNT_ID}.d.codeartifact.eu-central-1.amazonaws.com/npm/npm/"
}
}
# Taskfile.yaml
release:publish:
desc: Publish npm package to CodeArtifact
cmds:
- task: deps:login
- npm run build
- npm publish
Example: Python Package Publishing
# pyproject.toml
[project]
name = "ontopix-my-package"
version = "1.0.0"
[tool.poetry]
[[tool.poetry.source]]
name = "ontopix"
url = "https://ontopix-${AWS_ACCOUNT_ID}.d.codeartifact.eu-central-1.amazonaws.com/pypi/pypi/simple/"
# Taskfile.yaml
release:publish:
desc: Publish Python package to CodeArtifact
cmds:
- task: deps:login:pypi
- python -m build
- twine upload --repository ontopix dist/*
Variations
Variation 1: Per-Repository Role Restriction
For sensitive repositories, create dedicated roles with repository-specific trust policies:
resource "aws_iam_role" "codeartifact_sensitive_repo" {
name = "GitHubActions-CodeArtifact-SensitiveRepo"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Federated = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
# Exact repository match
"token.actions.githubusercontent.com:sub" = "repo:ontopix/sensitive-repo:ref:refs/tags/v*"
}
}
}]
})
}
Variation 2: GitHub Environments with Required Approvals
Add manual approval gates for production releases:
# .github/workflows/release.yaml
jobs:
publish:
runs-on: ubuntu-latest
environment:
name: production
url: https://npm.ontopix.com/@ontopix/my-package
# GitHub will require environment approval before running
Configure in GitHub: Settings → Environments → production → Required reviewers
Variation 3: Artifact Retention Policies
Configure lifecycle policies in CodeArtifact to manage storage costs:
# infra/global/codeartifact/main.tf
resource "aws_codeartifact_repository" "npm" {
# ... existing configuration ...
# Retention policy (requires AWS CLI or API)
provisioner "local-exec" {
command = <<-EOT
aws codeartifact put-package-origin-configuration \
--domain ${self.domain} \
--repository ${self.repository} \
--format npm \
--package-retention-policy '{"versionRetention": {"type": "COUNT", "value": 10}}'
EOT
}
}
Related Patterns
- GitHub Actions Workflows — How to structure CI/CD workflows
- Taskfile Contract — Taskfile as operational interface
- Infrastructure Layout — Where CodeArtifact infrastructure lives
- Repository Structure — Standard repository organization
References
Ontopix Resources
- Central infrastructure repository:
../infra/global/codeartifact/ - IAM policies:
CodeArtifactReadAccess,CodeArtifactWriteAccess - Domain:
ontopix(eu-central-1)
External Documentation
- AWS CodeArtifact Documentation
- GitHub OIDC with AWS
- aws-actions/configure-aws-credentials
- CodeArtifact npm Usage
- CodeArtifact Python Usage
Security Best Practices
- AWS Security Best Practices for CodeArtifact
- GitHub Actions Security Hardening
- OWASP: Using Components with Known Vulnerabilities
Status: ✅ Ready for Implementation
Last Updated: 2026-01-28
Infrastructure Status: Requires OIDC provider and role updates in infra/global/