Skip to content

Commit

Permalink
Handle authenticating CodeBuild against source repo
Browse files Browse the repository at this point in the history
  • Loading branch information
ipmb committed Dec 16, 2020
1 parent ee19676 commit d13103e
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 31 deletions.
143 changes: 112 additions & 31 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
Expand Down Expand Up @@ -344,51 +343,133 @@ func (a *App) GetBuildArtifact(build *codebuild.Build, name string) ([]byte, err

// GetConsoleURL generate a URL which will sign the user in to the AWS console and redirect to the desinationURL
func (a *App) GetConsoleURL(destinationURL string) (*string, error) {
credentials, err := a.Session.Config.Credentials.Get()
creds, err := a.Session.Config.Credentials.Get()
if err != nil {
return nil, err
}
sessionJSON, err := json.Marshal(map[string]string{
"sessionId": credentials.AccessKeyID,
"sessionKey": credentials.SecretAccessKey,
"sessionToken": credentials.SessionToken,
})
return auth.GetConsoleURL(&creds, destinationURL)
}

// DescribeTasks generate a URL which will sign the user in to the AWS console and redirect to the desinationURL
func (a *App) DescribeTasks() ([]*ecs.Task, error) {
err := a.LoadSettings()
if err != nil {
return nil, err
}
client := &http.Client{}
signinURL := "https://signin.aws.amazon.com/federation"
req, err := http.NewRequest("GET", signinURL, nil)
err = a.LoadDeployStatus()
if err != nil {
return nil, err
}
q := req.URL.Query()
q.Add("Action", "getSigninToken")
q.Add("SessionDuration", "3600")
q.Add("Session", fmt.Sprintf("%s", sessionJSON))
req.URL.RawQuery = q.Encode()
resp, err := client.Do(req)
signinResp := struct {
SigninToken string `json:"SigninToken"`
}{}
body, err := ioutil.ReadAll(resp.Body)
ecsSvc := ecs.New(a.Session)
taskARNs := []*string{}
for proc := range a.DeployStatus.Processes {
listTaskOutput, err := ecsSvc.ListTasks(&ecs.ListTasksInput{
Family: aws.String(fmt.Sprintf("%s-%s", a.Name, proc)),
Cluster: &a.Settings.Cluster.ARN,
})
if err != nil {
return nil, err
}
taskARNs = append(taskARNs, listTaskOutput.TaskArns...)
}
describeTasksOutput, err := ecsSvc.DescribeTasks(&ecs.DescribeTasksInput{
Tasks: taskARNs,
Cluster: &a.Settings.Cluster.ARN,
Include: []*string{aws.String("TAGS")},
})
if err != nil {
return nil, err
}
if err = json.Unmarshal(body, &signinResp); err != nil {
return nil, err
return describeTasksOutput.Tasks, nil
}

type Scaling struct {
CPU int `json:"cpu"`
Memory int `json:"memory"`
MinProcesses int `json:"min_processes"`
MaxProcesses int `json:"max_processes"`
}

func (a *App) ResizeProcess(processType string, cpu int, memory int) error {
ssmSvc := ssm.New(a.Session)
parameterName := fmt.Sprintf("/paaws/apps/%s/scaling", a.Name)
parameterOutput, err := ssmSvc.GetParameter(&ssm.GetParameterInput{
Name: &parameterName,
})
var scaling map[string]*Scaling
if err != nil {
scaling = map[string]*Scaling{}
} else {
if err = json.Unmarshal([]byte(*parameterOutput.Parameter.Value), &scaling); err != nil {
return err
}
}
_, ok := scaling[processType]
if !ok {
scaling[processType] = &Scaling{
CPU: 1024,
Memory: 2048,
MinProcesses: 1,
MaxProcesses: 1,
}
}
scaling[processType].CPU = cpu
scaling[processType].Memory = memory
scalingJSON, err := json.Marshal(scaling)
if err != nil {
return err
}
req, err = http.NewRequest("GET", signinURL, nil)
_, err = ssmSvc.PutParameter(&ssm.PutParameterInput{
Name: &parameterName,
Type: aws.String("String"),
Value: aws.String(fmt.Sprintf("%s", scalingJSON)),
Overwrite: aws.Bool(true),
})
if err != nil {
return nil, err
return err
}
q = req.URL.Query()
q.Add("Action", "login")
q.Add("Issuer", "AppPack.io")
q.Add("SigninToken", signinResp.SigninToken)
q.Add("Destination", destinationURL)
req.URL.RawQuery = q.Encode()
return aws.String(req.URL.String()), nil
return nil
}

func (a *App) ScaleProcess(processType string, processCount int) error {
ssmSvc := ssm.New(a.Session)
parameterName := fmt.Sprintf("/paaws/apps/%s/scaling", a.Name)
parameterOutput, err := ssmSvc.GetParameter(&ssm.GetParameterInput{
Name: &parameterName,
})
var scaling map[string]*Scaling
if err != nil {
scaling = map[string]*Scaling{}
} else {
if err = json.Unmarshal([]byte(*parameterOutput.Parameter.Value), &scaling); err != nil {
return err
}
}
_, ok := scaling[processType]
if !ok {
scaling[processType] = &Scaling{
CPU: 1024,
Memory: 2048,
MinProcesses: 1,
MaxProcesses: 1,
}
}
scaling[processType].MinProcesses = processCount
scaling[processType].MaxProcesses = processCount
scalingJSON, err := json.Marshal(scaling)
if err != nil {
return err
}
_, err = ssmSvc.PutParameter(&ssm.PutParameterInput{
Name: &parameterName,
Type: aws.String("String"),
Value: aws.String(fmt.Sprintf("%s", scalingJSON)),
Overwrite: aws.Bool(true),
})
if err != nil {
return err
}
return nil
}

// Init will pull in app settings from DyanmoDB and provide helper
Expand Down
12 changes: 12 additions & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path/filepath"

Expand All @@ -16,6 +17,7 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
awsconsoleurl "github.com/jkueh/go-aws-console-url"
)

const (
Expand Down Expand Up @@ -352,3 +354,13 @@ func WhoAmI() (*string, error) {
}
return &userInfo.Email, nil
}

// GetConsoleURL - Returns the sign-in URL
func GetConsoleURL(creds *credentials.Value, destinationURL string) (*string, error) {
token, err := awsconsoleurl.GetSignInToken(creds)
return aws.String(fmt.Sprintf(
"https://signin.aws.amazon.com/federation?Action=login&Destination=%s&SigninToken=%s",
url.QueryEscape(destinationURL),
token.Token,
)), err
}
63 changes: 63 additions & 0 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,27 @@ limitations under the License.
package cmd

import (
"bufio"
"fmt"
"math/rand"
"net/url"
"os"
"strings"
"time"

"github.com/AlecAivazis/survey/v2"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/aws/aws-sdk-go/service/codebuild"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/google/uuid"
"github.com/lincolnloop/apppack/auth"
"github.com/logrusorgru/aurora"
"github.com/mattn/go-isatty"
"github.com/pkg/browser"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
Expand Down Expand Up @@ -575,6 +581,61 @@ var createClusterCmd = &cobra.Command{
},
}

func verifySourceCredentials(sess *session.Session, repositoryType string, interactive bool) error {
codebuildSvc := codebuild.New(sess)
sourceCredentialsOutput, err := codebuildSvc.ListSourceCredentials(&codebuild.ListSourceCredentialsInput{})
if err != nil {
return err
}
hasCredentials := false
for _, cred := range sourceCredentialsOutput.SourceCredentialsInfos {
if *cred.ServerType == repositoryType {
hasCredentials = true
}
}
if !hasCredentials {
var friendlySourceName string
if repositoryType == "BITBUCKET" {
friendlySourceName = "Bitbucket"
} else {
friendlySourceName = "GitHub"
}
Spinner.Stop()
printWarning(fmt.Sprintf("CodeBuild needs to be authenticated to access your repository at %s", friendlySourceName))
fmt.Println("On the CodeBuild new project page:")
fmt.Printf(" 1. Scroll to the %s section\n", aurora.Bold("Source"))
fmt.Printf(" 2. Select %s for the %s\n", aurora.Bold(friendlySourceName), aurora.Bold("Source provider"))
fmt.Printf(" 3. Keep the default %s\n", aurora.Bold("Connect using OAuth"))
fmt.Printf(" 4. Click %s\n", aurora.Bold(fmt.Sprintf("Connect to %s", friendlySourceName)))
fmt.Printf(" 5. Click %s in the popup window\n\n", aurora.Bold("Confirm"))
newProjectURL := "https://console.aws.amazon.com/codesuite/codebuild/project/new"
if !interactive {
fmt.Printf("Visit %s to complete the authentication\n", newProjectURL)
fmt.Println("No further steps are necessary. After you've completed the authentication, re-run this command.")
os.Exit(1)
}
creds, err := sess.Config.Credentials.Get()
if err != nil {
return err
}
url, err := auth.GetConsoleURL(&creds, newProjectURL)
if err != nil {
return err
}
if isatty.IsTerminal(os.Stdin.Fd()) {
fmt.Println("Opening the CodeBuild new project page now...")
browser.OpenURL(*url)
} else {
fmt.Printf("Visit the following URL to authenticate: %s", *url)
}
reader := bufio.NewReader(os.Stdin)
fmt.Printf("Finish authentication in your web browser then press ENTER to continue.")
_, _ = reader.ReadString('\n')
return verifySourceCredentials(sess, repositoryType, interactive)
}
return nil
}

// appCmd represents the create command
var appCmd = &cobra.Command{
Use: "app",
Expand Down Expand Up @@ -657,6 +718,8 @@ var appCmd = &cobra.Command{
} else {
checkErr(fmt.Errorf("unknown repository source"))
}
err = verifySourceCredentials(sess, repositoryType, !nonInteractive)
checkErr(err)
rand.Seed(time.Now().UnixNano())
input := cloudformation.CreateStackInput{
StackName: aws.String(fmt.Sprintf("apppack-app-%s", *name)),
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/google/uuid v1.1.2
github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c // indirect
github.com/hokaccha/go-prettyjson v0.0.0-20190818114111-108c894c2c0e // indirect
github.com/jkueh/go-aws-console-url v0.0.1
github.com/kr/pty v1.1.8 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.35.7/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
github.com/aws/aws-sdk-go v1.35.35 h1:o/EbgEcIPWga7GWhJhb3tiaxqk4/goTdo5YEMdnVxgE=
github.com/aws/aws-sdk-go v1.35.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.36.8 h1:3nvY3Ax2RC6PN1i0OKppxjq3doHWqiYtvenLQ/oZ5jI=
Expand Down Expand Up @@ -130,6 +131,8 @@ github.com/hokaccha/go-prettyjson v0.0.0-20190818114111-108c894c2c0e h1:0aewS5NT
github.com/hokaccha/go-prettyjson v0.0.0-20190818114111-108c894c2c0e/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jkueh/go-aws-console-url v0.0.1 h1:3ylrzZxqK4XLCV7Vn/lRaVr/g4EWyofVPaf9KyV6tYw=
github.com/jkueh/go-aws-console-url v0.0.1/go.mod h1:zx0hfyXqRusIwHUzlSva1Mmn/DEJD6tHIWBUNrYa7wE=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
Expand Down Expand Up @@ -298,6 +301,7 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA=
Expand Down

0 comments on commit d13103e

Please sign in to comment.