Skip to content

Commit

Permalink
Add fly tokens create ssh command (superfly#3446)
Browse files Browse the repository at this point in the history
* add `fly tokens create ssh` command

* fix default name for ssh tokens

Co-authored-by: Jacob Fenton <[email protected]>

---------

Co-authored-by: Jacob Fenton <[email protected]>
  • Loading branch information
btoews and asib authored Apr 15, 2024
1 parent dc50ea1 commit c6eed58
Showing 1 changed file with 112 additions and 1 deletion.
113 changes: 112 additions & 1 deletion internal/command/tokens/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func newCreate() *cobra.Command {
newOrg(),
newOrgRead(),
newLiteFSCloud(),
newSSH(),
)

return cmd
Expand Down Expand Up @@ -72,6 +73,39 @@ func newOrg() *cobra.Command {
return cmd
}

func newSSH() *cobra.Command {
const (
short = "Create token for SSH'ing to a single app"
long = "Create token for SSH'ing to a single app. To be able to SSH to an app, this token is also allowed to connect to the org's wireguard network."
usage = "ssh"
)

cmd := command.New(usage, short, long, runSSH,
command.RequireSession,
command.LoadAppNameIfPresent,
)

flag.Add(cmd,
flag.App(),
flag.AppConfig(),
flag.JSONOutput(),
flag.String{
Name: "name",
Shorthand: "n",
Description: "Token name",
Default: "flyctl ssh token",
},
flag.Duration{
Name: "expiry",
Shorthand: "x",
Description: "The duration that the token will be valid",
Default: time.Hour * 24 * 365 * 20,
},
)

return cmd
}

func newOrgRead() *cobra.Command {
const (
short = "Create read-only org tokens"
Expand Down Expand Up @@ -186,7 +220,7 @@ func makeToken(ctx context.Context, apiClient *fly.Client, orgID string, expiry
expiry,
)
if err != nil {
return nil, fmt.Errorf("failed creating deploy token: %w", err)
return nil, fmt.Errorf("failed creating token: %w", err)
}
return resp, nil
}
Expand Down Expand Up @@ -222,6 +256,79 @@ func runOrg(ctx context.Context) error {
return nil
}

func runSSH(ctx context.Context) error {
var token string
apiClient := fly.ClientFromContext(ctx)

expiry := ""
if expiryDuration := flag.GetDuration(ctx, "expiry"); expiryDuration != 0 {
expiry = expiryDuration.String()
}

appName := appconfig.NameFromContext(ctx)

app, err := apiClient.GetAppCompact(ctx, appName)
if err != nil {
return fmt.Errorf("failed retrieving app %s: %w", appName, err)
}

// start with app deploy token and then pare it down.
resp, err := makeToken(ctx, apiClient, app.Organization.ID, expiry, "deploy", &gql.LimitedAccessTokenOptions{
"app_id": app.ID,
})
if err != nil {
return err
}

token = resp.CreateLimitedAccessToken.LimitedAccessToken.TokenHeader
macTok, disToks, err := flyio.ParsePermissionAndDischargeTokens(token)
if err != nil {
return fmt.Errorf("failed parsing token from API: %w", err)
}

// FindPermissionAndDischargeTokens returned parsed tokens, but we want to
// make two copies of the token and there's no API for doing a deep copy of
// a Macaroon.
orgAppReadMac, err := macaroon.Decode(macTok)
if err != nil {
return fmt.Errorf("failed decoding tokens from API: %w", err)
}

if err := orgAppReadMac.Add(ptr(resset.ActionRead)); err != nil {
return fmt.Errorf("failed to attenuate org-app-read token: %w", err)
}

orgAppReadTok, err := orgAppReadMac.Encode()
if err != nil {
return fmt.Errorf("failed encoding org-app-read token: %w", err)
}

mutationMac, err := macaroon.Decode(macTok)
if err != nil {
return fmt.Errorf("failed decoding tokens from API: %w", err)
}

if err := mutationMac.Add(&flyio.Mutations{Mutations: []string{"issueCertificate", "addWireGuardPeer"}}); err != nil {
return fmt.Errorf("failed to attenuate mutation token: %w", err)
}

mutationTok, err := mutationMac.Encode()
if err != nil {
return fmt.Errorf("failed encoding mutation token: %w", err)
}

token = macaroon.ToAuthorizationHeader(append([][]byte{orgAppReadTok, mutationTok}, disToks...)...)

io := iostreams.FromContext(ctx)
if config.FromContext(ctx).JSONOutput {
render.JSON(io.Out, map[string]string{"token": token})
} else {
fmt.Fprintln(io.Out, token)
}

return nil
}

func runOrgRead(ctx context.Context) error {
var (
token string
Expand Down Expand Up @@ -390,3 +497,7 @@ func runLiteFSCloud(ctx context.Context) (err error) {

return nil
}

func ptr[T any](t T) *T {
return &t
}

0 comments on commit c6eed58

Please sign in to comment.