Skip to content

Commit cd8e8a7

Browse files
committed
Initial Commit
1 parent 184e660 commit cd8e8a7

11 files changed

Lines changed: 381 additions & 2 deletions

File tree

.devcontainer/Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM mcr.microsoft.com/vscode/devcontainers/go:1.18
2+
ENV EDITOR=vim
3+
4+
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && \
5+
apt-get install -y --no-install-recommends vim gnupg2 ripgrep && \
6+
apt-get clean && rm -rf /var/lib/apt/lists/*

.devcontainer/devcontainer.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"build": {
3+
"dockerfile": "Dockerfile",
4+
},
5+
"extensions": [
6+
"eamodio.gitlens",
7+
"golang.Go",
8+
"ms-python.python",
9+
"ms-python.vscode-pylance",
10+
"GitHub.copilot",
11+
],
12+
"mounts": [
13+
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.aws/,target=/root/.aws,type=bind,consistency=cached"
14+
],
15+
"remoteEnv": {
16+
"AWS_DEFAULT_REGION": "ap-southeast-2",
17+
"AWS_PROFILE": "sktansandbox",
18+
"CODEARTIFACT_DOMAIN": "sktansandbox",
19+
"CODEARTIFACT_REPO": "sandbox",
20+
},
21+
"features": {
22+
// Used build and deploy docker containers
23+
"docker-in-docker": {
24+
"version": "latest",
25+
"moby": true
26+
},
27+
// Used for AWS CLI (currently interferes with my gpg commits due to a bug)
28+
// https://github.com/microsoft/vscode-dev-containers/pull/1391
29+
// Comments will be removed once ^ has been merged
30+
// "aws-cli": {
31+
// "version": "lts",
32+
// },
33+
// Testing out the proxy mechanism and for deploying via CDK
34+
"python": {
35+
"version": "lts",
36+
},
37+
"node": {
38+
"version": "lts",
39+
"nodeGypDependencies": true
40+
}
41+
},
42+
"postCreateCommand": ".devcontainer/post_create.sh"
43+
}

.devcontainer/post_create.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
3+
# Install x86 or ARM version of awscliv2 based on current machine architecture
4+
if [[ "$(uname -m)" == "x86_64" ]]; then
5+
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "/tmp/awscliv2.zip"
6+
else
7+
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "/tmp/awscliv2.zip"
8+
fi
9+
unzip /tmp/awscliv2.zip -d /tmp/awscliv2
10+
/tmp/awscliv2/aws/install && rm -rf /tmp/awscliv2*
11+
12+
npm install -g aws-cdk
13+
pip install pre-commit

.pre-commit-config.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v4.1.0 # Use the ref you want to point at
4+
hooks:
5+
- id: trailing-whitespace
6+
- id: end-of-file-fixer
7+
- id: mixed-line-ending
8+
# - id: no-commit-to-branch
9+
# name: Don't commit to master

Dockerfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM golang:1.18-alpine as app-builder
2+
WORKDIR /go/src/app
3+
4+
COPY src .
5+
6+
RUN CGO_ENABLED=0 go install -ldflags '-extldflags "-static"' -tags timetzdata
7+
8+
# Build actual image with the compiled app
9+
FROM scratch
10+
11+
LABEL maintainer="git@sktan.com"
12+
13+
COPY --from=app-builder /go/bin/aws-codeartifact-proxy /aws-codeartifact-proxy
14+
COPY --from=app-builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
15+
16+
ENTRYPOINT ["/aws-codeartifact-proxy"]

README.md

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,68 @@
1-
# aws-codeartifact-proxy
2-
An AWS code artifact proxy to allow unauthenticated access to your code artifacts
1+
# AWS Code Artifact Proxy
2+
3+
An AWS Code Artifact Proxy that allows you to point your package managers to Code Artifact without the need of managing credentials.
4+
## Why was this built?
5+
6+
Not every user who pulls code from your private codeartifact repository needs AWS credentials:
7+
- Users of CLI tooling you are deploying internally in your comapny
8+
- Developers of applications that don't interact with AWS but rely on a private Python / Node library.
9+
- Maybe you have firewalling requirements or want the ability to see which packages are being installed by your developers?
10+
11+
## Features:
12+
13+
Although I haven't been able to test them all (mostly because I don't use the languages), the proxy should support the following artifact types (replace `artifacts.example.com` with your deployed proxy hostname)
14+
15+
| Repository Type | Tested | URL |
16+
| --------------- | ------ | ------------------------------------- |
17+
| Pypi | Yes | https://artifacts.example.com/simple/ |
18+
| NPM | Yes | https://artifacts.example.com/ |
19+
| Maven | No | https://artifacts.example.com/ |
20+
| Nuget | No | https://artifacts.example.com/ |
21+
22+
Currently we only support choosing a single repository at launch, athough maybe in the future I will look at auto-resovling the request and figure out which repository to use based on the useragent. This should simplify setup.
23+
24+
## How to Use?
25+
26+
You can run this in three easy ways.
27+
28+
1. Download the release from the Github page, and run it on any linux server.
29+
2. Use the container `sktan/aws-codeartifact-proxy` and run it on any capable host (AWS ECS, AWS EC2, Linux / Windows VM)
30+
3. Use the pre-built CDK template found in the `cdk` directory and deploy it to your environment (requires Python)
31+
32+
By default, the proxy will choose to use the Pypi as it's type. If you would like to use a separate registry type, set your `CODEARTIFACT_TYPE` environment variable to one of the following:
33+
- pypi
34+
- npm
35+
- maven
36+
- nuget
37+
38+
Once you have started the proxy with valid AWS credentials (this uses the [default credential provider chain](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials)), you should receive similar output to this:
39+
40+
```
41+
2022/04/03 04:41:53 Authenticating against CodeArtifact
42+
2022/04/03 04:41:53 Authorization successful
43+
2022/04/03 04:41:53 Requests will now be proxied to https://sktansandbox-1234567890.d.codeartifact.ap-southeast-2.amazonaws.com/pypi/sandbox/
44+
```
45+
46+
And to test that it is working, using `pip` against the proxy should result in similar output:
47+
48+
```
49+
## CLI Output
50+
root ➜ /workspaces/aws-codeartifact-proxy (master ✗) $ pip download --index-url="http://localhost:8080/simple" --no-deps boto3
51+
Looking in indexes: http://localhost:8080/simple
52+
Collecting boto3
53+
Downloading http://localhost:8080/simple/boto3/1.21.32/boto3-1.21.32-py3-none-any.whl (132 kB)
54+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 132.4/132.4 KB 20.6 MB/s eta 0:00:00
55+
Saved ./boto3-1.21.32-py3-none-any.whl
56+
Successfully downloaded boto3
57+
58+
## Proxy Output
59+
2022/04/03 04:52:44 REQ: 127.0.0.1:52066 GET "/simple/boto3/" "pip/22.0.4 ...."
60+
2022/04/03 04:52:44 Sending request to https://sktansandbox-1234567890.d.codeartifact.ap-southeast-2.amazonaws.com/pypi/sandbox/simple/boto3/
61+
2022/04/03 04:52:44 RES: 127.0.0.1:52066 "GET" 200 "/simple/boto3/" "pip/22.0.4 ...."
62+
```
63+
64+
## Contributing
65+
66+
If you'd like to contribute to this project, please feel free to raise a pull request. I would highly recommend using the devcontainer setup in this repo, as it will provide you a working development environment.
67+
68+
If you find any bugs, please raise it as a Github issue and I will have a look at it.

src/go.mod

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module github.com/sktan/aws-codeartifact-proxy
2+
3+
go 1.18
4+
5+
require (
6+
github.com/aws/aws-sdk-go-v2 v1.16.2
7+
github.com/aws/aws-sdk-go-v2/config v1.15.3
8+
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.12.3
9+
)
10+
11+
require (
12+
github.com/aws/aws-sdk-go-v2/credentials v1.11.2 // indirect
13+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 // indirect
14+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 // indirect
15+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 // indirect
16+
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 // indirect
17+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 // indirect
18+
github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 // indirect
19+
github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 // indirect
20+
github.com/aws/smithy-go v1.11.2 // indirect
21+
)

src/go.sum

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
github.com/aws/aws-sdk-go-v2 v1.16.2 h1:fqlCk6Iy3bnCumtrLz9r3mJ/2gUT0pJ0wLFVIdWh+JA=
2+
github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
3+
github.com/aws/aws-sdk-go-v2/config v1.15.3 h1:5AlQD0jhVXlGzwo+VORKiUuogkG7pQcLJNzIzK7eodw=
4+
github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg=
5+
github.com/aws/aws-sdk-go-v2/credentials v1.11.2 h1:RQQ5fzclAKJyY5TvF+fkjJEwzK4hnxQCLOu5JXzDmQo=
6+
github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g=
7+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 h1:LWPg5zjHV9oz/myQr4wMs0gi4CjnDN/ILmyZUFYXZsU=
8+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk=
9+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 h1:onz/VaaxZ7Z4V+WIN9Txly9XLTmoOh1oJ8XcAC3pako=
10+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM=
11+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 h1:9stUQR/u2KXU6HkFJYlqnZEjBnbgrVbG6I5HN09xZh0=
12+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM=
13+
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 h1:by9P+oy3P/CwggN4ClnW2D4oL91QV7pBzBICi1chZvQ=
14+
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE=
15+
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.12.3 h1:0KKzUiM72Xp4iG52EvLGk7ULmstaSjZydOhsVlMQh+4=
16+
github.com/aws/aws-sdk-go-v2/service/codeartifact v1.12.3/go.mod h1:Uhp1Vx6ZJ5rbR+PFi1DxhPhcRKgS4euvqVF04x2X458=
17+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 h1:Gh1Gpyh01Yvn7ilO/b/hr01WgNpaszfbKMUgqM186xQ=
18+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw=
19+
github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 h1:frW4ikGcxfAEDfmQqWgMLp+F1n4nRo9sF39OcIb5BkQ=
20+
github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU=
21+
github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 h1:cJGRyzCSVwZC7zZZ1xbx9m32UnrKydRYhOvcD1NYP9Q=
22+
github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8=
23+
github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE=
24+
github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
25+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
26+
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
27+
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
28+
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
29+
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
30+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
31+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
32+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
33+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
34+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
35+
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

src/main.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package main
2+
3+
import (
4+
// Should this be called tools? There isn't really much going on here...
5+
"github.com/sktan/aws-codeartifact-proxy/tools"
6+
)
7+
8+
func main() {
9+
// Do an initial authentication so that we can initialise the proxy properly
10+
tools.Authenticate()
11+
12+
// Run a goroutine to check for reauthentication to the CodeArtifact Service
13+
go tools.CheckReauth()
14+
15+
// Start the Proxy listener so that we can intercept the requests
16+
tools.ProxyInit()
17+
}

src/tools/aws.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package tools
2+
3+
import (
4+
"context"
5+
"log"
6+
"os"
7+
"time"
8+
9+
"github.com/aws/aws-sdk-go-v2/aws"
10+
"github.com/aws/aws-sdk-go-v2/config"
11+
"github.com/aws/aws-sdk-go-v2/service/codeartifact"
12+
"github.com/aws/aws-sdk-go-v2/service/codeartifact/types"
13+
)
14+
15+
type CodeArtifactAuthInfoStruct struct {
16+
Url string
17+
AuthorizationToken string
18+
LastAuth time.Time
19+
}
20+
21+
var CodeArtifactAuthInfo = &CodeArtifactAuthInfoStruct{}
22+
23+
// Authenticate performs the authentication against CodeArtifact and caches the credentials
24+
func Authenticate() {
25+
log.Printf("Authenticating against CodeArtifact")
26+
27+
// Authenticate against CodeArtifact
28+
cfg, cfgErr := config.LoadDefaultConfig(context.TODO())
29+
if cfgErr != nil {
30+
log.Fatalf("unable to load SDK config, %v", cfgErr)
31+
}
32+
svc := codeartifact.NewFromConfig(cfg)
33+
34+
// TODO: auto-resolve these via Environment variables
35+
codeArtDomain := aws.String(os.Getenv("CODEARTIFACT_DOMAIN"))
36+
codeArtOwner, codeArtOwnerFound := os.LookupEnv("CODEARTIFACT_OWNER")
37+
codeArtRepos := aws.String(os.Getenv("CODEARTIFACT_REPO"))
38+
39+
// Resolve Package Format from the environment variable (defaults to pypi)
40+
codeArtTypeS, found := os.LookupEnv("CODEARTIFACT_TYPE")
41+
if !found || codeArtTypeS == "" {
42+
codeArtTypeS = "pypi"
43+
}
44+
var codeArtTypeT types.PackageFormat
45+
if codeArtTypeS == "pypi" {
46+
codeArtTypeT = types.PackageFormatPypi
47+
} else if codeArtTypeS == "maven" {
48+
codeArtTypeT = types.PackageFormatMaven
49+
} else if codeArtTypeS == "npm" {
50+
codeArtTypeT = types.PackageFormatNpm
51+
} else if codeArtTypeS == "nuget" {
52+
codeArtTypeT = types.PackageFormatNuget
53+
}
54+
55+
// Create the input for the CodeArtifact API
56+
authInput := &codeartifact.GetAuthorizationTokenInput{
57+
DurationSeconds: aws.Int64(3600),
58+
Domain: codeArtDomain,
59+
}
60+
if codeArtOwnerFound {
61+
authInput.DomainOwner = aws.String(codeArtOwner)
62+
}
63+
authResp, authErr := svc.GetAuthorizationToken(context.TODO(), authInput)
64+
if authErr != nil {
65+
log.Fatalf("unable to get authorization token, %v", authErr)
66+
}
67+
log.Printf("Authorization successful")
68+
CodeArtifactAuthInfo.AuthorizationToken = *authResp.AuthorizationToken
69+
CodeArtifactAuthInfo.LastAuth = time.Now()
70+
71+
// Get the URL for the CodeArtifact Service
72+
urlInput := &codeartifact.GetRepositoryEndpointInput{
73+
Domain: codeArtDomain,
74+
Format: codeArtTypeT,
75+
Repository: codeArtRepos,
76+
}
77+
if codeArtOwnerFound {
78+
urlInput.DomainOwner = aws.String(codeArtOwner)
79+
}
80+
81+
urlResp, urlErr := svc.GetRepositoryEndpoint(context.TODO(), urlInput)
82+
if urlErr != nil {
83+
log.Fatalf("unable to get repository endpoint, %v", urlErr)
84+
}
85+
CodeArtifactAuthInfo.Url = *urlResp.RepositoryEndpoint
86+
87+
log.Printf("Requests will now be proxied to %s", CodeArtifactAuthInfo.Url)
88+
}
89+
90+
// CheckReauth checks if we have not yet authenticated, or need to authenticate within the next 15 minutes
91+
func CheckReauth() {
92+
for {
93+
if CodeArtifactAuthInfo.AuthorizationToken == "" || time.Now().Sub(CodeArtifactAuthInfo.LastAuth) > 45*time.Minute {
94+
log.Printf("%d minutes until the CodeArtifact token expires, attempting a reauth.", time.Now().Sub(CodeArtifactAuthInfo.LastAuth)/time.Minute)
95+
Authenticate()
96+
}
97+
// Sleep for 15 seconds for the next check
98+
time.Sleep(15 * time.Second)
99+
}
100+
}

0 commit comments

Comments
 (0)