Skip to content

Commit

Permalink
begin integrating github auth
Browse files Browse the repository at this point in the history
  • Loading branch information
djelusic committed Feb 24, 2022
1 parent eb19d41 commit 1570e27
Show file tree
Hide file tree
Showing 21 changed files with 341 additions and 71 deletions.
2 changes: 2 additions & 0 deletions cli/cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ func bindAwsInstallFlags(cmd *cobra.Command, a *controller.SetupArgs) {
cmd.Flags().BoolVar(&a.UseEnv, "aws-env", false, "Use AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION environment variables for AWS authentication")
cmd.Flags().StringVar(&a.Profile, "aws-profile", "", "Use the given profile for AWS authentication")
cmd.Flags().BoolVar(&a.DryRun, "dry-run", false, "Don't start install/uninstall just show what credentials will be used")
cmd.Flags().StringVar(&a.GithubUser, "github-user", "", "The GitHub user that owns the node")
cmd.Flags().StringVar(&a.GithubOrg, "github-org", "", "The GitHub organization that owns the node")
}

func showAwsDryRunInfo(a *controller.SetupArgs) {
Expand Down
101 changes: 101 additions & 0 deletions cli/controller/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package controller

import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"net/url"

"github.com/mantil-io/mantil.go/logs"
"github.com/mantil-io/mantil/cli/log"
"github.com/mantil-io/mantil/cli/secret"
"github.com/mantil-io/mantil/domain"
"github.com/nats-io/nats.go"
"github.com/pkg/browser"
)

const (
clientID = "db4946aabe86cd6c126e"
)

func authToken(n *domain.Node) (string, error) {
t, err := n.AuthToken()
var terr *domain.TokenExpiredError
if errors.As(err, &terr) && n.GitHubAuthEnabled {
var err error
t, err = githubAuth(n)
if err != nil {
return "", log.Wrap(err)
}
n.JWT = t
} else if err != nil {
return "", log.Wrap(err)
}
return t, nil
}

func githubAuth(n *domain.Node) (string, error) {
s, err := createState(n)
if err != nil {
return "", err
}
if err := githubLogin(s); err != nil {
return "", err
}
t, err := waitToken(s.Inbox)
if err != nil {
return "", err
}
return t, nil
}

type state struct {
Inbox string `json:"inbox"`
NodeEndpoint string `json:"node_endpoint"`
}

func createState(n *domain.Node) (*state, error) {
inbox := nats.NewInbox()
s := state{
Inbox: inbox,
NodeEndpoint: n.Endpoints.Rest,
}
return &s, nil
}

func githubLogin(state *state) error {
u, err := url.Parse("https://github.com/login/oauth/authorize")
if err != nil {
return err
}
q := u.Query()
q.Set("client_id", clientID)
buf, err := json.Marshal(state)
if err != nil {
return err
}
sb64 := base64.StdEncoding.EncodeToString([]byte(buf))
q.Set("state", sb64)
u.RawQuery = q.Encode()
return browser.OpenURL(u.String())
}

func waitToken(inbox string) (string, error) {
rsp := struct {
JWT string `json:"jwt"`
}{}
lc := logs.ListenerConfig{
ListenerJWT: secret.LogsListenerCreds,
Subject: inbox,
Rsp: &rsp,
}
l, err := logs.NewLambdaListener(lc)
if err != nil {
return "", err
}
if err := l.Done(context.Background()); err != nil {
return "", err
}
return rsp.JWT, nil
}
6 changes: 3 additions & 3 deletions cli/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import (
"text/template"
"time"

"github.com/mantil-io/mantil/kit/aws"
"github.com/mantil-io/mantil/cli/controller/invoke"
"github.com/mantil-io/mantil/cli/log"
"github.com/mantil-io/mantil/cli/ui"
"github.com/mantil-io/mantil/domain"
"github.com/mantil-io/mantil/kit/aws"
"github.com/mantil-io/mantil/node/dto"
"github.com/olekukonko/tablewriter"
)
Expand Down Expand Up @@ -78,7 +78,7 @@ func awsClient(node *domain.Node, stage *domain.Stage) (*aws.AWS, error) {
url.RawQuery = q.Encode()

token := func() string {
token, err := node.AuthToken()
token, err := authToken(node)
if err != nil {
return ""
}
Expand All @@ -93,7 +93,7 @@ func awsClient(node *domain.Node, stage *domain.Stage) (*aws.AWS, error) {
}

func nodeInvoker(node *domain.Node) (*invoke.HTTPClient, error) {
token, err := node.AuthToken()
token, err := authToken(node)
if err != nil {
return nil, log.Wrap(err)
}
Expand Down
14 changes: 13 additions & 1 deletion cli/controller/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type Setup struct {
credentialsProvider int
force bool
yes bool
ghUser string
ghOrg string
}

type stackTemplateData struct {
Expand Down Expand Up @@ -70,6 +72,8 @@ func NewSetup(a *SetupArgs) (*Setup, error) {
credentialsProvider: a.credentialsProvider,
force: a.Force,
yes: a.Yes,
ghUser: a.GithubUser,
ghOrg: a.GithubOrg,
}, nil
}

Expand All @@ -81,7 +85,8 @@ Available regions are:
}
ws := c.store.Workspace()
bucket, key := getPath(c.aws.Region())
n, err := ws.NewNode(c.nodeName, c.aws.AccountID(), c.aws.Region(), bucket, key, version)
ghAuth := c.ghOrg != "" || c.ghUser != ""
n, err := ws.NewNode(c.nodeName, c.aws.AccountID(), c.aws.Region(), bucket, key, version, ghAuth)
if err != nil {
return log.Wrap(err)
}
Expand Down Expand Up @@ -130,13 +135,18 @@ func (c *Setup) create(n *domain.Node) error {
NamingTemplate: n.ResourceNamingTemplate(),
APIGatewayLogsRole: APIGatewayLogsRole,
ResourceTags: c.resourceTags,
GithubUser: c.ghUser,
GithubOrg: c.ghOrg,
}
rsp := &dto.SetupResponse{}
if err := invoke.Lambda(c.aws.Lambda(), c.lambdaName, ui.NodeLogsSink).Do("create", req, rsp); err != nil {
return log.Wrap(err, "failed to invoke setup function")
}
n.Endpoints.Rest = rsp.APIGatewayRestURL
n.CliRole = rsp.CliRole
if n.GitHubAuthEnabled {
n.JWT = rsp.JWT
}
infrastructureDuration := tmr()

log.Event(domain.Event{NodeCreate: &domain.NodeEvent{
Expand Down Expand Up @@ -224,6 +234,8 @@ func (c *Setup) upgrade(n *domain.Node) error {
ResourceSuffix: n.ResourceSuffix(),
NamingTemplate: n.ResourceNamingTemplate(),
ResourceTags: c.resourceTags,
GithubUser: c.ghUser,
GithubOrg: c.ghOrg,
}
if err := invoke.Lambda(c.aws.Lambda(), c.lambdaName, ui.NodeLogsSink).Do("upgrade", req, nil); err != nil {
return log.Wrap(err, "failed to invoke setup function")
Expand Down
7 changes: 6 additions & 1 deletion cli/controller/setup_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package controller
import (
"os"

"github.com/mantil-io/mantil/kit/aws"
"github.com/mantil-io/mantil/cli/log"
"github.com/mantil-io/mantil/domain"
"github.com/mantil-io/mantil/kit/aws"
)

const (
Expand All @@ -28,6 +28,8 @@ type SetupArgs struct {
credentialsProvider int
Force bool
Yes bool
GithubUser string
GithubOrg string
}

func DefaultNodeName() string { return domain.DefaultNodeName }
Expand All @@ -53,6 +55,9 @@ func (a *SetupArgs) validate() error {
a.credentialsProvider = domain.AWSCredentialsByProfile
return nil
}
if a.GithubOrg != "" && a.GithubUser != "" {
return log.Wrap(NewArgumentError("cannot set both `github-user` and `github-org`"))
}
return log.Wrap(NewArgumentError("AWS credentials not provided"))
}

Expand Down
1 change: 1 addition & 0 deletions cli/controller/setup_stack_template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Resources:
- ssm:GetParameter
- ssm:GetParameters
- ssm:DescribeParameters
- ssm:DeleteParameter
Resource:
- "*"
MantilSetupLambda:
Expand Down
15 changes: 5 additions & 10 deletions domain/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package domain
import (
"encoding/json"
"fmt"
"os"
"reflect"
"strings"

Expand Down Expand Up @@ -33,23 +32,19 @@ const (
Member
)

func ReadAccessToken(headers map[string]string) (*AccessTokenClaims, error) {
func ReadAccessToken(headers map[string]string, publicKey string) (*AccessTokenClaims, error) {
if at, ok := headers[AccessTokenHeader]; ok {
return decodeAccessToken(at)
return decodeAccessToken(at, publicKey)
}
if at, ok := headers[strings.ToLower(AccessTokenHeader)]; ok {
return decodeAccessToken(at)
return decodeAccessToken(at, publicKey)
}
return nil, fmt.Errorf("access token not found in %s header", AccessTokenHeader)
}

func decodeAccessToken(at string) (*AccessTokenClaims, error) {
key, ok := os.LookupEnv(EnvPublicKey)
if !ok {
return nil, fmt.Errorf("key not found in environment variable %s", EnvPublicKey)
}
func decodeAccessToken(at, pk string) (*AccessTokenClaims, error) {
var claims AccessTokenClaims
if err := token.Decode(at, key, &claims); err != nil {
if err := token.Decode(at, pk, &claims); err != nil {
return nil, err
}
return &claims, nil
Expand Down
12 changes: 12 additions & 0 deletions domain/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,15 @@ type EnvironmentConfigValidationError struct {
func (e *EnvironmentConfigValidationError) Error() string {
return e.Err.Error()
}

type TokenExpiredError struct{}

func (e *TokenExpiredError) Error() string {
return fmt.Sprintf("token expired")
}

type SSMPathNotFoundError struct{}

func (e *SSMPathNotFoundError) Error() string {
return fmt.Sprintf("SSM parameter path not found")
}
Loading

0 comments on commit 1570e27

Please sign in to comment.