Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions internal/dataplane/service_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ import (
// commands when the platform token cannot call the runtime route directly.
const CLIServiceKeyName = "volcano-cli-data-plane"

// serviceKeyTokenPrefix identifies a data-plane service key (mirrors the server's
// auth.ServiceKeyPrefix). A caller already holding such a key is authenticated
// for the data plane directly, so the reserved-key list/create is neither needed
// nor possible (control-plane routes reject a service key with 401).
const serviceKeyTokenPrefix = "sk-"

// cliDataPlanePermissions is the least-privilege scope requested for the reserved
// data-plane key: function invocation and storage object I/O only. It must cover
// every operation the CLI performs with this key (copy/move/set-visibility map
Expand Down Expand Up @@ -60,6 +66,13 @@ func (s Service) ServiceKeyForProject(ctx context.Context, project *clisession.P
if project == nil {
return "", errors.New("project session is required")
}
// When the session is already authenticated with a service key (e.g.
// VOLCANO_TOKEN is a scoped data-plane key), use it directly. It is already a
// data-plane credential, and resolving the reserved CLI key would require a
// control-plane list/create that a service key is not permitted to make (401).
if token := sessionServiceKey(project); token != "" {
return token, nil
}
Comment on lines +69 to +75
name := s.serviceKeyName()
key, found, err := s.findServiceKey(ctx, project, name)
if err != nil {
Expand All @@ -79,6 +92,17 @@ func (s Service) ServiceKeyForProject(ctx context.Context, project *clisession.P
return serviceKeyPlaintext(created, name)
}

func sessionServiceKey(project *clisession.ProjectSession) string {
if project.Config == nil {
return ""
}
token := strings.TrimSpace(project.Config.Token())
if strings.HasPrefix(token, serviceKeyTokenPrefix) {
return token
}
return ""
}

func (s Service) serviceKeyName() string {
name := strings.TrimSpace(s.keyName)
if name == "" {
Expand Down
20 changes: 20 additions & 0 deletions internal/dataplane/service_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,26 @@ func TestServiceKeyReloadsAfterCreateConflict(t *testing.T) {
assert.Equal(t, 2, listHits)
}

func TestServiceKeyUsesConfiguredServiceKeyDirectly(t *testing.T) {
setServiceKeyTestHome(t)
saveServiceKeyTestConfig(t)
// The caller already holds a scoped data-plane service key (e.g. in CI via
// VOLCANO_TOKEN); the CLI must use it as-is and never attempt the reserved-key
// list/create, which a service key cannot perform against control-plane routes.
t.Setenv("VOLCANO_TOKEN", "sk-provided-data-plane-key")

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Errorf("unexpected API call %s %s: a provided service key must be used without a control-plane lookup", r.Method, r.URL.Path)
http.Error(w, "unexpected control-plane call", http.StatusUnauthorized)
}))
defer server.Close()

service := NewService(cliruntime.Deps{HTTPClient: server.Client(), APIBaseURL: server.URL})
key, err := service.ServiceKey(t.Context())
require.NoError(t, err)
assert.Equal(t, "sk-provided-data-plane-key", key)
}

func setServiceKeyTestHome(t *testing.T) {
t.Helper()
t.Setenv("HOME", t.TempDir())
Expand Down
Loading