Skip to content

Commit e7833af

Browse files
authored
Merge pull request #42 from arashnd/support-aks-workload-identity
Add support for AKS workload identity
2 parents f879da0 + d8ce8cc commit e7833af

26 files changed

+850
-39
lines changed

.github/workflows/test.yml

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
- uses: actions/checkout@v4
3636
- uses: ./.github/actions/setup-terraform
3737
- name: Terraform apply
38-
run: terraform apply -auto-approve -var "create_vm=true" -var "create_app_service=true" -var "ssh_key=${{ secrets.SSH_PUBLIC_KEY }}"
38+
run: terraform apply -auto-approve -var "create_vm=true" -var "create_app_service=true" -var "create_aks=true" -var "ssh_key=${{ secrets.SSH_PUBLIC_KEY }}"
3939

4040
app_service_test:
4141
needs: deploy-infrastructure
@@ -97,6 +97,45 @@ jobs:
9797
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
9898
run: bundle exec rake test_azure_vm
9999

100+
aks_test:
101+
needs: deploy-infrastructure
102+
runs-on: ubuntu-latest
103+
steps:
104+
- name: Install dependencies
105+
run: sudo apt-get update && sudo apt-get install -y libvips sshuttle sqlite3 libsqlite3-dev
106+
- name: SSH key
107+
env:
108+
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
109+
run: |
110+
mkdir -p /home/runner/.ssh
111+
echo "${{ secrets.SSH_PRIVATE_KEY }}" > /home/runner/.ssh/id_rsa
112+
chmod 600 /home/runner/.ssh/id_rsa
113+
ssh-agent -a $SSH_AUTH_SOCK > /dev/null
114+
ssh-add /home/runner/.ssh/id_rsa
115+
- name: Checkout
116+
uses: actions/checkout@v4
117+
- uses: ./.github/actions/setup-terraform
118+
- name: Azure login
119+
uses: azure/login@v2
120+
with:
121+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
122+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
123+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
124+
- name: Setup kubectl
125+
uses: azure/setup-kubectl@v4
126+
- name: Setup ruby
127+
uses: ruby/setup-ruby@v1
128+
with:
129+
bundler-cache: true
130+
- name: Tests
131+
env:
132+
AZURE_ACCOUNT_NAME: ${{secrets.AZURE_ACCOUNT_NAME}}
133+
AZURE_PRIVATE_CONTAINER: ${{secrets.AZURE_PRIVATE_CONTAINER}}
134+
AZURE_PUBLIC_CONTAINER: ${{secrets.AZURE_PUBLIC_CONTAINER}}
135+
USE_MANAGED_IDENTITIES: true
136+
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
137+
run: bundle exec rake test_aks
138+
100139
azurite_test:
101140
runs-on: ubuntu-latest
102141
steps:

.terraform.lock.hcl

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## [Unreleased]
22

3+
- Add support for AKS workload identity
4+
35
## [0.5.9.1] 2025-06-30
46

57
- Fix a regression in #13 + fix add a test

Dockerfile.vpn-test

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
FROM ruby:3.2.3-slim
2+
3+
RUN apt-get update && apt-get install -y --no-install-recommends \
4+
build-essential \
5+
git \
6+
curl \
7+
unzip \
8+
sshuttle \
9+
iptables \
10+
openssh-client \
11+
libsqlite3-dev \
12+
libvips-dev \
13+
python3 \
14+
gnupg \
15+
lsb-release \
16+
lsof \
17+
&& rm -rf /var/lib/apt/lists/*
18+
19+
RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash
20+
21+
RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
22+
&& install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl \
23+
&& rm kubectl
24+
25+
RUN TERRAFORM_VERSION="1.9.8" \
26+
&& curl -fsSL "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" -o terraform.zip \
27+
&& unzip terraform.zip \
28+
&& install -o root -g root -m 0755 terraform /usr/local/bin/terraform \
29+
&& rm terraform.zip
30+
31+
WORKDIR /app
32+
33+
COPY Gemfile Gemfile.lock azure-blob.gemspec ./
34+
COPY lib/azure_blob/version.rb ./lib/azure_blob/version.rb
35+
36+
RUN bundle install
37+
38+
COPY . .
39+
40+
ENTRYPOINT ["/app/bin/docker-vpn-test"]

Gemfile.lock

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ GEM
8282
base64 (0.2.0)
8383
bcrypt_pbkdf (1.1.1)
8484
bcrypt_pbkdf (1.1.1-arm64-darwin)
85+
bcrypt_pbkdf (1.1.1-x86_64-darwin)
8586
benchmark (0.4.0)
8687
bigdecimal (3.1.9)
8788
builder (3.3.0)
@@ -97,6 +98,7 @@ GEM
9798
erb (5.0.1)
9899
erubi (1.13.1)
99100
ffi (1.17.2-arm64-darwin)
101+
ffi (1.17.2-x86_64-darwin)
100102
ffi (1.17.2-x86_64-linux-gnu)
101103
globalid (1.2.1)
102104
activesupport (>= 6.1)
@@ -140,11 +142,13 @@ GEM
140142
timeout
141143
net-smtp (0.5.1)
142144
net-protocol
143-
net-ssh (7.2.3)
145+
net-ssh (7.3.0)
144146
nio4r (2.7.4)
145-
nokogiri (1.18.8-arm64-darwin)
147+
nokogiri (1.18.10-arm64-darwin)
146148
racc (~> 1.4)
147-
nokogiri (1.18.8-x86_64-linux-gnu)
149+
nokogiri (1.18.10-x86_64-darwin)
150+
racc (~> 1.4)
151+
nokogiri (1.18.10-x86_64-linux-gnu)
148152
racc (~> 1.4)
149153
parallel (1.24.0)
150154
parser (3.3.1.0)
@@ -244,6 +248,7 @@ GEM
244248
logger
245249
securerandom (0.4.1)
246250
sqlite3 (2.6.0-arm64-darwin)
251+
sqlite3 (2.6.0-x86_64-darwin)
247252
sqlite3 (2.6.0-x86_64-linux-gnu)
248253
stringio (3.1.7)
249254
strscan (3.1.0)
@@ -262,6 +267,7 @@ GEM
262267

263268
PLATFORMS
264269
arm64-darwin-24
270+
x86_64-darwin-23
265271
x86_64-linux
266272

267273
DEPENDENCIES

README.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,13 @@ microsoft:
2626

2727
### Managed Identity (Entra ID)
2828

29-
AzureBlob supports managed identities on :
29+
AzureBlob supports managed identities on:
3030
- Azure VM
3131
- App Service
32+
- AKS (Azure Kubernetes Service) with workload identity
3233
- Azure Functions (Untested but should work)
3334
- Azure Containers (Untested but should work)
3435

35-
AKS support will likely require more work. Contributions are welcome.
36-
3736
To authenticate through managed identities instead of a shared key, omit `storage_access_key` from your `storage.yml` file and pass in the identity `principal_id`.
3837

3938
ActiveStorage config example:
@@ -46,6 +45,21 @@ prod:
4645
principal_id: 71b34410-4c50-451d-b456-95ead1b18cce
4746
```
4847

48+
#### AKS with Workload Identity
49+
50+
ActiveStorage config example:
51+
52+
```
53+
prod:
54+
service: AzureBlob
55+
container: container_name
56+
storage_account_name: account_name
57+
use_managed_identities: true
58+
```
59+
60+
> uses `AZURE_CLIENT_ID`, `AZURE_TENANT_ID` and `AZURE_FEDERATED_TOKEN_FILE` environment variables, made available by AKS cluster when Azure AD Workload Identity is set up properly.
61+
62+
4963
### Azurite
5064

5165
To use Azurite, pass the `storage_blob_host` config key with the Azurite URL (`http://127.0.0.1:10000/devstoreaccount1` by default)
@@ -126,20 +140,26 @@ A dev environment is supplied through Nix with [devenv](https://devenv.sh/).
126140

127141
To test with Entra ID, the `AZURE_ACCESS_KEY` environment variable must be unset and the code must be ran or proxied through a VPS with the proper roles.
128142

129-
For cost saving, the terraform variable `create_vm` and `create_app_service` are false by default.
130-
To create the VPS and App service, Create a var file `var.tfvars` containing:
143+
For cost saving, the terraform variables `create_vm`, `create_app_service`, and `create_aks` are false by default.
144+
To create the VM, App Service, and/or AKS cluster, create a var file `var.tfvars` containing:
131145

132146
```
133147
create_vm = true
134148
create_app_service = true
149+
create_aks = true
135150
```
136151
and re-apply terraform: `terraform apply -var-file=var.tfvars`.
137152

138-
This will create the VPS and required managed identities.
153+
This will create the infrastructure and required managed identities.
154+
155+
**Testing:**
156+
- `bin/rake test_azure_vm` - Establishes a VPN connection to the Azure VM and runs tests using node identity
157+
- `bin/rake test_app_service` - Establishes a VPN connection to the App Service container and runs tests
158+
- `bin/rake test_aks` - Establishes a VPN connection to the AKS cluster and runs tests using workload identity
139159

140-
`bin/rake test_azure_vm` and `bin/rake test_app_service` will establish a VPN connection to the VM or App service container and run the test suite. You might be prompted for a sudo password when the VPN starts (sshuttle).
160+
You might be prompted for a sudo password when the VPN starts (sshuttle).
141161

142-
After you are done, run terraform again without the var file (`terraform apply`) to destroy the VPS and App service application.
162+
After you are done, run terraform again without the var file (`terraform apply`) to destroy all resources.
143163

144164
#### Cleanup
145165

Rakefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ require "minitest/test_task"
55
require "azure_blob"
66
require_relative "test/support/app_service_vpn"
77
require_relative "test/support/azure_vm_vpn"
8+
require_relative "test/support/aks_vpn"
89
require_relative "test/support/azurite"
910

1011
Minitest::TestTask.create(:test_rails) do
@@ -49,6 +50,16 @@ ensure
4950
vpn.kill
5051
end
5152

53+
task :test_aks do |t|
54+
vpn = AksVpn.new
55+
ENV["AZURE_CLIENT_ID"] = vpn.client_id
56+
ENV["AZURE_TENANT_ID"] = vpn.tenant_id
57+
ENV["AZURE_FEDERATED_TOKEN_FILE"] = vpn.token_file
58+
Rake::Task["test_entra_id"].execute
59+
ensure
60+
vpn.kill
61+
end
62+
5263
task :test_azurite do |t|
5364
azurite = Azurite.new
5465
# Azurite well-known credentials

bin/docker-vpn-test

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
4+
usage() {
5+
echo "Usage: $0 <test_type>"
6+
echo ""
7+
echo "Test types:"
8+
echo " aks - Run AKS VPN tests"
9+
echo " azure_vm - Run Azure VM VPN tests"
10+
echo " app_service - Run App Service VPN tests"
11+
exit 1
12+
}
13+
14+
if [ -z "$1" ]; then
15+
usage
16+
fi
17+
18+
TEST_TYPE="$1"
19+
20+
case "$TEST_TYPE" in
21+
aks|azure_vm|app_service)
22+
;;
23+
*)
24+
echo "Unknown test type: $TEST_TYPE"
25+
usage
26+
;;
27+
esac
28+
29+
cd "$(dirname "$0")/.."
30+
31+
bundle exec rake "test_${TEST_TYPE}"

bin/proxy-aks

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Get Terraform outputs
5+
resource_group=$(terraform output --raw "resource_group")
6+
cluster_name=$(terraform output --raw "aks_cluster_name")
7+
ssh_username=$(terraform output --raw "aks_ssh_username")
8+
9+
# Setup kubectl config
10+
echo "Setting up kubectl config..."
11+
az aks get-credentials --resource-group "$resource_group" --name "$cluster_name" --overwrite-existing --only-show-errors 2>/dev/null
12+
13+
# Wait for pod to be ready
14+
echo "Waiting for SSH pod to be ready..."
15+
kubectl wait --for=condition=ready pod -l app=azure-blob-test --timeout=300s
16+
17+
# Extract workload identity token
18+
echo "Extracting workload identity token..."
19+
POD_NAME=$(kubectl get pod -l app=azure-blob-test -o jsonpath='{.items[0].metadata.name}')
20+
kubectl exec "$POD_NAME" -- cat /var/run/secrets/azure/tokens/azure-identity-token > /tmp/azure-identity-token
21+
22+
# Kill any existing process on port 2222
23+
lsof -ti:2222 | xargs kill -9 2>/dev/null || true
24+
25+
# Start port forward in the background
26+
echo "Setting up port forward..."
27+
kubectl port-forward service/azure-blob-test-ssh 2222:22 &
28+
PORT_FORWARD_PID=$!
29+
30+
# Cleanup function
31+
cleanup() {
32+
echo "Cleaning up..."
33+
kill $PORT_FORWARD_PID 2>/dev/null || true
34+
}
35+
trap cleanup EXIT
36+
37+
# Wait for port forward to be established
38+
sleep 3
39+
40+
# Establish sshuttle VPN
41+
echo "Establishing VPN connection..."
42+
exec sshuttle -e "ssh -o CheckHostIP=no -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --python=python3 -r "$ssh_username@127.0.0.1:2222" 0/0

0 commit comments

Comments
 (0)