mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:12:02 +00:00
feat/sg: add 'sg sams client create' (#63885)
Adds an equivalent to the curl command we currently share, but in `sg`. If we add a better API around this later it's just an in-place replacement. Similar to https://github.com/sourcegraph/sourcegraph/pull/63883 this "just works" with zero configuration against SAMS-dev. Part https://linear.app/sourcegraph/issue/CORE-220, a spike into polishing some local-dev DX for SAMS. ## Test plan ``` sg sams client create -redirect-uris='https://sourcegraph.test:3443/.auth/callback' robert-testing ``` if you hit an error loading the secret, e.g. targeting the prod instance, you get a suggestion to get Entitle access: ``` sg sams client create -redirect-uris='https://sourcegraph.test:3443/.auth/callback' -sams='https://accounts.sourcegraph.com' robert-testing ⚠️ Running sg with a dev build, following flags have different default value unless explictly set: skip-auto-update, disable-analytics 👉 Failed to get secret - do you have Entitle access to the "sourcegraph-accounts-prod-csvc" project? See https://sourcegraph.notion.site/Sourcegraph-Accounts-infrastructure-operations-b90a571da30443a8b1e7c31ade3594fb ❌ google(sourcegraph-accounts-prod-csvc): failed to get secret "MANAGEMENT_SECRET": rpc error: code = PermissionDenied desc = Permission 'secretmanager.versions.access' denied for resource 'projects/sourcegraph-accounts-prod-csvc/secrets/MANAGEMENT_SECRET/versions/latest' (or it may not exist). ``` ## Changelog - `sg sams client create` can now be used to create IdP clients for SAMS. --------- Co-authored-by: Erik Seliger <erikseliger@me.com>
This commit is contained in:
parent
f53e211804
commit
30d50b72a2
@ -7,8 +7,10 @@ go_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//dev/sg/internal/category",
|
||||
"//dev/sg/internal/secrets",
|
||||
"//dev/sg/internal/std",
|
||||
"//dev/sg/sams/samsflags",
|
||||
"//internal/httpcli",
|
||||
"//lib/errors",
|
||||
"@com_github_sourcegraph_sourcegraph_accounts_sdk_go//scopes",
|
||||
"@com_github_urfave_cli_v2//:cli",
|
||||
|
||||
@ -23,7 +23,10 @@ func ClientCredentials() []cli.Flag {
|
||||
Aliases: []string{"sams"},
|
||||
EnvVars: []string{"SG_SAMS_SERVER_URL"},
|
||||
Value: SAMSDevURL,
|
||||
Usage: fmt.Sprintf("URL of the Sourcegraph Accounts Management System (SAMS) server - one of %q or %q",
|
||||
Usage: fmt.Sprintf("URL of the Sourcegraph Accounts Management System (SAMS) server - one of %q or %q,"+
|
||||
// TODO: 9091 currently conflicts with embeddings, we may want
|
||||
// to change the default in the future
|
||||
" or http://127.0.0.1:9091 for a locally running server.",
|
||||
SAMSProdURL, SAMSDevURL),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
|
||||
@ -2,13 +2,21 @@
|
||||
package sams
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph-accounts-sdk-go/scopes"
|
||||
"github.com/sourcegraph/sourcegraph/dev/sg/internal/category"
|
||||
"github.com/sourcegraph/sourcegraph/dev/sg/internal/secrets"
|
||||
"github.com/sourcegraph/sourcegraph/dev/sg/internal/std"
|
||||
"github.com/sourcegraph/sourcegraph/internal/httpcli"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/dev/sg/sams/samsflags"
|
||||
@ -81,5 +89,133 @@ Please reach out to #discuss-core-services for assistance if you have any questi
|
||||
return std.Out.WriteCode("json", string(data))
|
||||
},
|
||||
}},
|
||||
}, {
|
||||
Name: "client",
|
||||
Usage: "Manage IdP clients registered in SAMS",
|
||||
Subcommands: []*cli.Command{{
|
||||
Name: "create",
|
||||
ArgsUsage: "<display name>",
|
||||
Usage: "Create an IdP client in the target SAMS instance",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "redirect-uris",
|
||||
Usage: "Redirect URIs to associate with the client, e.g. 'https://sourcegraph.test:3443/.auth/callback'",
|
||||
Required: true,
|
||||
},
|
||||
// Flags are special subset of clientCredentialsFlags(), as we
|
||||
// use a static secret - in the future we should change this to
|
||||
// require client credentials instead.
|
||||
&cli.StringFlag{
|
||||
Name: "sams-server",
|
||||
Aliases: []string{"sams"},
|
||||
EnvVars: []string{"SG_SAMS_SERVER_URL"},
|
||||
Value: samsflags.SAMSDevURL,
|
||||
Usage: fmt.Sprintf("URL of the Sourcegraph Accounts Management System (SAMS) server - one of %q or %q",
|
||||
samsflags.SAMSProdURL, samsflags.SAMSDevURL),
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "scopes",
|
||||
Aliases: []string{"s"},
|
||||
Value: cli.NewStringSlice("openid", "profile", "email"),
|
||||
Usage: "OAuth scopes ('$SERVICE::$PERM::$ACTION') to request from the Sourcegraph Accounts Management System (SAMS) server",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
samsServer := c.String("sams-server")
|
||||
displayName := c.Args().First()
|
||||
if displayName == "" {
|
||||
return errors.New("argument display name required")
|
||||
}
|
||||
|
||||
ss, err := secrets.FromContext(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Environments specified in
|
||||
// https://github.com/sourcegraph/managed-services/blob/main/services/sourcegraph-accounts/service.yaml
|
||||
samsManagementSecretSource := map[string]*secrets.ExternalSecret{
|
||||
samsflags.SAMSDevURL: {
|
||||
Project: "sourcegraph-dev",
|
||||
Name: "SAMS_MANAGEMENT_SECRET",
|
||||
},
|
||||
samsflags.SAMSProdURL: {
|
||||
Project: "sourcegraph-accounts-prod-csvc",
|
||||
Name: "MANAGEMENT_SECRET",
|
||||
},
|
||||
}
|
||||
var managementSecret string
|
||||
if externalSecret := samsManagementSecretSource[samsServer]; externalSecret != nil {
|
||||
std.Out.WriteSuggestionf("Targeting SAMS instance at %q", samsServer)
|
||||
managementSecret, err = ss.GetExternal(c.Context, *externalSecret)
|
||||
if err != nil {
|
||||
std.Out.WriteAlertf("Failed to get secret - do you have Entitle access to the %q project? See https://sourcegraph.notion.site/Sourcegraph-Accounts-infrastructure-operations-b90a571da30443a8b1e7c31ade3594fb",
|
||||
externalSecret.Project)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
managementSecret, err = std.Out.PromptPasswordf(os.Stdin,
|
||||
"Enter the SAMS management secret for your target SAMS server %q: ",
|
||||
c.String("sams-server"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
body := map[string]any{
|
||||
"name": displayName,
|
||||
"scopes": c.StringSlice("scopes"),
|
||||
"redirect_uris": c.StringSlice("redirect-uris"),
|
||||
}
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(
|
||||
http.MethodPost,
|
||||
samsServer+"/api/management/v1/identity-provider/clients",
|
||||
bytes.NewReader(data),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+managementSecret)
|
||||
|
||||
resp, err := httpcli.UncachedExternalDoer.Do(req.WithContext(c.Context))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "do request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
responseData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read response")
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
std.Out.Write("Got responnse:")
|
||||
std.Out.Write(string(responseData))
|
||||
return errors.Newf("got unexpected response status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var v any
|
||||
if err := json.Unmarshal(responseData, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
prettyResponseData, err := json.MarshalIndent(v, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := std.Out.WriteCode("json", string(prettyResponseData)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if u, err := url.Parse(samsServer); err == nil &&
|
||||
(u.Hostname() == "127.0.0.1" || u.Hostname() == "localhost") { // CI:LOCALHOST_OK
|
||||
std.Out.WriteSuggestionf("These client credentials can NOT be shown again - if you lose them you will need to create another one.")
|
||||
} else {
|
||||
std.Out.WriteWarningf("These client credentials are highly sensitive and can NOT be shown again. Please store them securely in Google Secret Manager or 1Password.")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user