git-remote-entire: ENTIRE_TOKEN env override for CI / workload identity · Entire
git-remote-entire: ENTIRE_TOKEN env override for CI / workload identity
8d53bc4→main·
toothbrush·1mo ago·3 files·+176 added/-18 removed
Let ENTIRE_TOKEN=
When set, derive the home-region core URL from the token's URL-shaped aud
claim (login/sa-session JWTs carry aud=
tokens.ParseClaims already exposes Audience (normalised across string and array forms), so no helper port was needed.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
2f3a007d8690View transcript
Changes
3
cmd
entire/cli/auth
Aenv_token.go+39
Aenv_token_test.go+92
git-remote-entire
Mmain.go+45/-18
package auth
import (
"fmt"
"net/url"
"strings"
"github.com/entireio/auth-go/tokens"
)
// EnvTokenVar is the environment variable that, when set, bypasses
// contexts.json and the keyring entirely: its value is used verbatim as the
// login JWT for repo-scoped token exchange. This is the CI / workload-identity
// path — a runner injects a short-lived login or sa-session JWT and clones
// without an interactive `entire login`.
const EnvTokenVar = "ENTIRE_TOKEN"
// CoreURLFromEnvToken derives the home-region core URL from an ENTIRE_TOKEN
// JWT's audience claim. Login and sa-session JWTs carry aud=<home-region URL>,
// which is what STS routing keys on — so we read aud, not iss (iss may be a
// regional core that can't mint the cross-region exchange).
//
// The aud claim is URL-shaped (scheme://host[/path]). It may be a single
// string or an array (RFC 7519 §4.1.3); ParseClaims normalises both to a
// slice. The first URL-shaped audience wins. A token with no URL-shaped aud
// is rejected with a clear error rather than silently falling back to context
// resolution, so a misconfigured CI token fails loudly.
func CoreURLFromEnvToken(rawToken string) (string, error) {
claims, err := tokens.ParseClaims(rawToken)
if err != nil {
return "", fmt.Errorf("parse %s claims: %w", EnvTokenVar, err)
}
for _, aud := range claims.Audience {
if u, err := url.Parse(aud); err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != "" {
return strings.TrimRight(aud, "/"), nil
}
}
return "", fmt.Errorf("%s must be a login or sa-session JWT whose aud is the home-region URL; found no URL-shaped audience claim", EnvTokenVar)
}
package auth
import (
"encoding/base64"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCoreURLFromEnvToken(t *testing.T) {
t.Parallel()
tests := []struct {
name string
aud any // nil omits the aud claim entirely
want string
wantErr bool
}{
{
name: "string aud",
aud: "https://core.us.entire.io",
want: "https://core.us.entire.io",
},
{
name: "string aud trailing slash trimmed",
aud: "https://core.us.entire.io/",
want: "https://core.us.entire.io",
},
{
name: "array aud picks first URL-shaped",
aud: []string{"entire-cli", "https://core.eu.entire.io"},
want: "https://core.eu.entire.io",
},
{
name: "opaque string aud rejected",
aud: "some-opaque-audience",
wantErr: true,
},
{
name: "array of opaque audiences rejected",
aud: []string{"aud-a", "aud-b"},
wantErr: true,
},
{
name: "missing aud rejected",
aud: nil,
wantErr: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
payload := map[string]any{"sub": "ci-runner"}
if tc.aud != nil {
payload["aud"] = tc.aud
}
raw, err := json.Marshal(payload)
require.NoError(t, err)
token := makeJWT(t, string(raw))
got, err := CoreURLFromEnvToken(token)
if tc.wantErr {
require.Error(t, err)
assert.Contains(t, err.Error(), EnvTokenVar)
return
}
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
}
func TestCoreURLFromEnvToken_MalformedToken(t *testing.T) {
t.Parallel()
_, err := CoreURLFromEnvToken("not-a-jwt")
require.Error(t, err)
assert.Contains(t, err.Error(), EnvTokenVar)
}
func TestCoreURLFromEnvToken_RejectsAlgNone(t *testing.T) {
t.Parallel()
// alg:none with a URL-shaped aud must still be rejected at the parse layer.
enc := base64.RawURLEncoding
token := enc.EncodeToString([]byte(`{"alg":"none"}`)) + "." +
enc.EncodeToString([]byte(`{"aud":"https://core.us.entire.io"}`)) + "."
_, err := CoreURLFromEnvToken(token)
require.Error(t, err)
assert.Contains(t, err.Error(), EnvTokenVar)
}