db: accept tskey-client- prefix for OAuth client auth

The upstream tailscale client only runs its OAuth client-credentials exchange
for secrets prefixed tskey-client-, so accept it as an alias for hskey-client-.
The prefix is only a label sliced off before lookup, so the same stored client
authenticates under either; lets the official client and GitHub Action mint auth
keys against headscale.
This commit is contained in:
Kristoffer Dalby committed 2026-09-25 17:09:19 +02:00
1 parent c5dc3d4385
commit 69e84c356d
3 files changed
+62 -1

No files matched your search

+6 -1
View File
@@ -192,7 +192,12 @@ func (hsdb *HSDatabase) AuthenticateOAuthClient(secretStr string) (*types.OAuthC
// used directly as an auth key; strip them before parsing.
secretStr, _, _ = strings.Cut(secretStr, "?")
_, rest, found := strings.Cut(secretStr, types.OAuthClientPrefix)
// See [types.TailscaleOAuthClientPrefix] for why the alias is accepted.
rest, found := strings.CutPrefix(secretStr, types.OAuthClientPrefix)
if !found {
rest, found = strings.CutPrefix(secretStr, types.TailscaleOAuthClientPrefix)
}
if !found {
return nil, ErrOAuthClientFailedToParse
}
+48
View File
@@ -86,6 +86,54 @@ func TestOAuthClientCreateAndAuthenticate(t *testing.T) {
require.Error(t, err)
}
// TestOAuthClientAuthenticateTailscalePrefix asserts the same stored client
// authenticates under the tskey-client- alias, and that only a leading prefix
// is recognised.
func TestOAuthClientAuthenticateTailscalePrefix(t *testing.T) {
db, err := newSQLiteTestDB()
require.NoError(t, err)
secret, client, err := db.CreateOAuthClient(
[]string{"auth_keys"},
[]string{"tag:ci"},
"",
nil,
)
require.NoError(t, err)
rest := strings.TrimPrefix(secret, types.OAuthClientPrefix)
tsSecret := types.TailscaleOAuthClientPrefix + rest
for _, s := range []string{
tsSecret,
// Callers may pass the raw auth-key form; ?attributes are stripped.
tsSecret + "?baseURL=http://127.0.0.1:8080&ephemeral=true",
} {
got, err := db.AuthenticateOAuthClient(s)
require.NoError(t, err, s)
assert.Equal(t, client.ClientID, got.ClientID)
}
// A wrong secret under the alias parses but fails verification.
_, err = db.AuthenticateOAuthClient(
types.TailscaleOAuthClientPrefix + client.ClientID + "-" + strings.Repeat("0", 64),
)
require.Error(t, err)
require.NotErrorIs(t, err, ErrOAuthClientFailedToParse)
for _, s := range []string{
types.TailscaleOAuthClientPrefix,
"tskey-auth-" + rest,
"tskey-" + rest,
"junk-" + tsSecret,
"junk-" + secret,
types.TailscaleOAuthClientPrefix + secret,
} {
_, err := db.AuthenticateOAuthClient(s)
require.ErrorIs(t, err, ErrOAuthClientFailedToParse, s)
}
}
func TestHashSecretRoundTrip(t *testing.T) {
const secret = "a-high-entropy-credential-secret"