Skip to content

Commit 9ccea9c

Browse files
authored
Merge pull request #1 from github/initial-commit
Initial Import
2 parents a6bdcf6 + 6c34689 commit 9ccea9c

34 files changed

+2059
-1
lines changed

.devcontainer/Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.224.3/containers/go/.devcontainer/base.Dockerfile
2+
3+
# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.16, 1.17, 1-bullseye, 1.16-bullseye, 1.17-bullseye, 1-buster, 1.16-buster, 1.17-buster
4+
ARG VARIANT="1.18-bullseye"
5+
FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT}
6+
7+
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
8+
ARG NODE_VERSION="none"
9+
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
10+
11+
# [Optional] Uncomment this section to install additional OS packages.
12+
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
13+
# && apt-get -y install --no-install-recommends <your-package-list-here>
14+
15+
# [Optional] Uncomment the next lines to use go get to install anything else you need
16+
# USER vscode
17+
# RUN go get -x <your-dependency-or-tool>
18+
19+
# [Optional] Uncomment this line to install global node packages.
20+
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

.devcontainer/devcontainer.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2+
// https://github.com/microsoft/vscode-dev-containers/tree/v0.224.3/containers/go
3+
{
4+
"name": "Go",
5+
"build": {
6+
"dockerfile": "Dockerfile",
7+
"args": {
8+
// Update the VARIANT arg to pick a version of Go: 1, 1.18, 1.17
9+
// Append -bullseye or -buster to pin to an OS version.
10+
// Use -bullseye variants on local arm64/Apple Silicon.
11+
"VARIANT": "1-bullseye",
12+
// Options
13+
"NODE_VERSION": "lts/*"
14+
}
15+
},
16+
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
17+
18+
// Set *default* container specific settings.json values on container create.
19+
"settings": {
20+
"go.toolsManagement.checkForUpdates": "local",
21+
"go.useLanguageServer": true,
22+
"go.gopath": "/go"
23+
},
24+
25+
// Add the IDs of extensions you want installed when the container is created.
26+
"extensions": [
27+
"golang.Go"
28+
],
29+
30+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
31+
// "forwardPorts": [],
32+
33+
// Use 'postCreateCommand' to run commands after the container is created.
34+
// "postCreateCommand": "go version",
35+
36+
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
37+
"remoteUser": "vscode",
38+
"features": {
39+
"git": "os-provided",
40+
"github-cli": "latest"
41+
}
42+
}

.github/workflows/action-test.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
name: Test Debugger Action
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
jobs:
10+
oidc_debug_test:
11+
permissions:
12+
contents: read
13+
id-token: write
14+
runs-on: ubuntu-latest
15+
name: A test of the oidc debugger
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v3
19+
- name: Debug OIDC Claims
20+
uses: ./
21+
with:
22+
audience: 'https://github.com/github'

.github/workflows/main.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
name: Test OIDC Debugger
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
jobs:
10+
test:
11+
permissions:
12+
contents: read
13+
id-token: write
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: git checkout
17+
uses: actions/checkout@v3
18+
- name: run oidc-debug.go
19+
run: go run cmd/oidc-debug.go -audience "https://github.com/github"

Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM alpine:latest
2+
RUN apk add --no-cache go
3+
4+
COPY . .
5+
6+
ENTRYPOINT ["/entrypoint.sh"]

README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,25 @@
11
# actions-oidc-debugger
2-
An Action for printing OIDC claims in GitHub Actions.
2+
3+
This action requests a JWT and prints the claims included within the JWT received from GitHub Actions.
4+
5+
## Usage
6+
7+
```yaml
8+
9+
on: [pull_request]
10+
11+
jobs:
12+
oidc_debug_test:
13+
permissions:
14+
contents: read
15+
id-token: write
16+
runs-on: ubuntu-latest
17+
name: A test of the oidc debugger
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v3
21+
- name: Debug OIDC Claims
22+
uses: github/actions-oidc-debugger@v1
23+
with:
24+
audience: 'https://github.com/github
25+
```

action.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: 'OIDC Debugger'
2+
description: 'Print the GitHub Actions OIDC claims.'
3+
inputs:
4+
audience:
5+
description: 'The audience to use when requesting the JWT.'
6+
required: true
7+
runs:
8+
using: 'docker'
9+
image: 'Dockerfile'
10+
args:
11+
- ${{ inputs.audience }}

actionsoidc/actions-oidc.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package actionsoidc
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"log"
8+
"net/http"
9+
"net/url"
10+
"os"
11+
12+
"github.com/golang-jwt/jwt"
13+
)
14+
15+
type ActionsOIDCClient struct {
16+
// the url to fetch the jwt
17+
TokenRequestURL string
18+
// the audience for the jwt
19+
Audience string
20+
// the token used to retrieve the jwt, not the jwt
21+
RequestToken string
22+
}
23+
24+
type ActionsJWT struct {
25+
Count int
26+
Value string
27+
ParsedToken *jwt.Token
28+
}
29+
30+
func GetEnvironmentVariable(e string) (string, error) {
31+
value := os.Getenv(e)
32+
if value == "" {
33+
return "", fmt.Errorf("missing %s from envrionment", e)
34+
}
35+
return value, nil
36+
}
37+
38+
func QuitOnErr(e error) {
39+
if e != nil {
40+
log.Fatal(e)
41+
}
42+
}
43+
44+
// construct a new ActionsOIDCClient
45+
func NewActionsOIDCClient(tokenURL string, audience string, token string) (ActionsOIDCClient, error) {
46+
c := ActionsOIDCClient{
47+
TokenRequestURL: tokenURL,
48+
Audience: audience,
49+
RequestToken: token,
50+
}
51+
err := c.BuildTokenURL()
52+
return c, err
53+
}
54+
55+
func DefaultOIDCClient(audience string) ActionsOIDCClient {
56+
tokenURL, err := GetEnvironmentVariable("ACTIONS_ID_TOKEN_REQUEST_URL")
57+
QuitOnErr(err)
58+
token, err := GetEnvironmentVariable("ACTIONS_ID_TOKEN_REQUEST_TOKEN")
59+
QuitOnErr(err)
60+
61+
c, err := NewActionsOIDCClient(tokenURL, audience, token)
62+
QuitOnErr(err)
63+
64+
return c
65+
}
66+
67+
// this function uses an ActionsOIDCClient to build the complete URL
68+
// to request a jwt
69+
func (c *ActionsOIDCClient) BuildTokenURL() error {
70+
parsed_url, err := url.Parse(c.TokenRequestURL)
71+
if err != nil {
72+
return fmt.Errorf("failed to parse URL: %w", err)
73+
}
74+
75+
if c.Audience != "" {
76+
query := parsed_url.Query()
77+
query.Set("audience", c.Audience)
78+
parsed_url.RawQuery = query.Encode()
79+
}
80+
return nil
81+
}
82+
83+
// retrieve an actions oidc token
84+
func (c *ActionsOIDCClient) GetJWT() (*ActionsJWT, error) {
85+
request, err := http.NewRequest("GET", c.TokenRequestURL, nil)
86+
if err != nil {
87+
return nil, err
88+
}
89+
90+
request.Header.Set("Authorization", "Bearer "+c.RequestToken)
91+
92+
var httpClient http.Client
93+
response, err := httpClient.Do(request)
94+
if err != nil {
95+
return nil, err
96+
}
97+
defer response.Body.Close()
98+
99+
if response.StatusCode != http.StatusOK {
100+
return nil, fmt.Errorf("received non-200 from jwt api: %s", http.StatusText((response.StatusCode)))
101+
}
102+
103+
rawBody, err := ioutil.ReadAll(response.Body)
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
var jwt ActionsJWT
109+
err = json.Unmarshal(rawBody, &jwt)
110+
111+
return &jwt, err
112+
}
113+
114+
func (j *ActionsJWT) Parse() {
115+
j.ParsedToken, _ = jwt.Parse(j.Value, func(token *jwt.Token) (interface{}, error) {
116+
// Don't forget to validate the alg is what you expect:
117+
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
118+
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
119+
}
120+
121+
// we don't need a real check here
122+
return []byte{}, nil
123+
})
124+
}
125+
126+
func (j *ActionsJWT) PrettyPrintClaims() string {
127+
if claims, ok := j.ParsedToken.Claims.(jwt.MapClaims); ok {
128+
jsonClaims, err := json.MarshalIndent(claims, "", " ")
129+
if err != nil {
130+
fmt.Println(fmt.Errorf("%w", err))
131+
}
132+
return string(jsonClaims)
133+
}
134+
return ""
135+
}

cmd/oidc-debug.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
7+
"github.com/github/actions-oidc-debugger/actionsoidc"
8+
)
9+
10+
func main() {
11+
12+
audience := flag.String("audience", "https://github.com/", "the audience for the requested jwt")
13+
flag.Parse()
14+
15+
if *audience == "https://github.com/" {
16+
actionsoidc.QuitOnErr(fmt.Errorf("-audience cli argument must be specified"))
17+
}
18+
19+
c := actionsoidc.DefaultOIDCClient(*audience)
20+
jwt, err := c.GetJWT()
21+
actionsoidc.QuitOnErr(err)
22+
23+
jwt.Parse()
24+
fmt.Print(jwt.PrettyPrintClaims())
25+
}

entrypoint.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/sh -l
2+
3+
go run cmd/oidc-debug.go -audience $1

0 commit comments

Comments
 (0)