diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 7cee6c2..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,41 +0,0 @@ -version: 2 - -jobs: - build: - working_directory: /go/src/github.com/allcloud-io/clisso - environment: # environment variables for the build itself - TEST_RESULTS: /tmp/test-results # path to where test results will be saved - docker: - - image: circleci/golang:1.11 - steps: - - checkout - - run: mkdir -p $TEST_RESULTS # create the test results directory - - - restore_cache: # restores saved cache if no changes are detected since last run - keys: - - v1-pkg-cache - - # Normally, this step would be in a custom primary image; - # we've added it here for the sake of explanation. - - run: go get github.com/jstemmer/go-junit-report - - run: - name: Ensure packages are installed - command: dep ensure - - save_cache: # Store cache in the /go/pkg directory - key: v1-pkg-cache - paths: - - "/go/pkg" - - run: - name: Run tests - command: | - trap "go-junit-report <${TEST_RESULTS}/go-test.out > ${TEST_RESULTS}/go-test-report.xml" EXIT - make test | tee ${TEST_RESULTS}/go-test.out - - run: - name: build for all target - command: make all - - store_artifacts: # Upload test summary for display in Artifacts - path: /tmp/test-results - destination: raw-test-output - - - store_test_results: # Upload test results for display in Test Summary - path: /tmp/test-results diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d207b18 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.go text eol=lf diff --git a/.github/workflow/upload_assets.yml b/.github/workflow/upload_assets.yml new file mode 100644 index 0000000..9efbc02 --- /dev/null +++ b/.github/workflow/upload_assets.yml @@ -0,0 +1,26 @@ +on: + release: + types: [created] + +name: Upload release assets after release is created +jobs: + build: + name: build binaries + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: 1.15.x + - name: Checkout code + uses: actions/checkout@v2 + - name: build + run: | + echo "GO111MODULE=on" >> $GITHUB_ENV + make zip + - name: Upload release assets + uses: skx/github-action-publish-binaries@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: './assets/*' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..1df8f69 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,51 @@ +on: + push: + branches: + - master + pull_request: + branches: + - master + +name: run tests +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: 1.15 + - name: Checkout code + uses: actions/checkout@v2 + - name: Run linters + uses: golangci/golangci-lint-action@v2 + with: + version: v1.29 + + test: + strategy: + matrix: + go: [1.15] + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/setup-go@v1 + with: + go-version: ${{ matrix.go }} + - uses: actions/checkout@v2 + - run: go test -v -coverprofile=profile ./... + + - name: Send coverage + uses: shogo82148/actions-goveralls@v1 + with: + path-to-profile: profile + flag-name: Platform-${{ matrix.platform }} + parallel: true + # notifies that all test jobs are finished. + finish: + needs: test + runs-on: ubuntu-latest + steps: + - uses: shogo82148/actions-goveralls@v1 + with: + parallel-finished: true diff --git a/README.md b/README.md index e70f06a..13df17f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Clisso: CLI Single Sign-On +[![Coverage Status](https://coveralls.io/repos/github/allcloud-io/clisso/badge.svg)](https://coveralls.io/github/allcloud-io/clisso) + Clisso (pronounced `/ˈklIsoʊ/`) allows you to retrieve temporary credentials for cloud platforms by authenticating with an identity provider (IdP). diff --git a/aws/aws.go b/aws/aws.go index 3f462d0..0085661 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -37,10 +37,22 @@ func WriteToFile(c *Credentials, filename string, section string) error { return err } cfg.DeleteSection(section) - cfg.Section(section).NewKey("aws_access_key_id", c.AccessKeyID) - cfg.Section(section).NewKey("aws_secret_access_key", c.SecretAccessKey) - cfg.Section(section).NewKey("aws_session_token", c.SessionToken) - cfg.Section(section).NewKey(expireKey, c.Expiration.UTC().Format(time.RFC3339)) + _, err = cfg.Section(section).NewKey("aws_access_key_id", c.AccessKeyID) + if err != nil { + return err + } + _, err = cfg.Section(section).NewKey("aws_secret_access_key", c.SecretAccessKey) + if err != nil { + return err + } + _, err = cfg.Section(section).NewKey("aws_session_token", c.SessionToken) + if err != nil { + return err + } + _, err = cfg.Section(section).NewKey(expireKey, c.Expiration.UTC().Format(time.RFC3339)) + if err != nil { + return err + } // Remove expired credentials. for _, s := range cfg.Sections() { diff --git a/cmd/apps.go b/cmd/apps.go index fa67f55..c7de211 100644 --- a/cmd/apps.go +++ b/cmd/apps.go @@ -18,7 +18,7 @@ var duration int // OneLogin var appID string -// Okta +// URL holds the Okta URL var URL string func init() { @@ -26,15 +26,15 @@ func init() { cmdAppsCreateOneLogin.Flags().StringVar(&appID, "app-id", "", "OneLogin app ID") cmdAppsCreateOneLogin.Flags().StringVar(&provider, "provider", "", "Name of the Clisso provider") cmdAppsCreateOneLogin.Flags().IntVar(&duration, "duration", 0, "(Optional) Session duration in seconds") - cmdAppsCreateOneLogin.MarkFlagRequired("app-id") - cmdAppsCreateOneLogin.MarkFlagRequired("provider") + mandatoryFlag(cmdAppsCreateOneLogin, "app-id") + mandatoryFlag(cmdAppsCreateOneLogin, "provider") // Okta cmdAppsCreateOkta.Flags().StringVar(&provider, "provider", "", "Name of the Clisso provider") cmdAppsCreateOkta.Flags().StringVar(&URL, "url", "", "Okta app URL") cmdAppsCreateOkta.Flags().IntVar(&duration, "duration", 0, "(Optional) Session duration in seconds") - cmdAppsCreateOkta.MarkFlagRequired("provider") - cmdAppsCreateOkta.MarkFlagRequired("url") + mandatoryFlag(cmdAppsCreateOkta, "provider") + mandatoryFlag(cmdAppsCreateOkta, "url") // Build command tree RootCmd.AddCommand(cmdApps) diff --git a/cmd/get.go b/cmd/get.go index d21124e..fadea43 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -29,7 +29,10 @@ func init() { &writeToFile, "write-to-file", "w", "", "Write credentials to this file instead of the default ($HOME/.aws/credentials)", ) - viper.BindPFlag("global.credentials-path", cmdGet.Flags().Lookup("write-to-file")) + err := viper.BindPFlag("global.credentials-path", cmdGet.Flags().Lookup("write-to-file")) + if err != nil { + log.Fatalf(color.RedString("Error binding flag global.credentials-path: %v"), err) + } } // processCredentials prints the given Credentials to a file and/or to the shell. diff --git a/cmd/helpers.go b/cmd/helpers.go new file mode 100644 index 0000000..68d21e1 --- /dev/null +++ b/cmd/helpers.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "log" + + "github.com/fatih/color" + "github.com/spf13/cobra" +) + +func mandatoryFlag(cmd *cobra.Command, name string) { + err := cmd.MarkFlagRequired(name) + if err != nil { + log.Fatalf(color.RedString("Error marking flag %s as required: %v"), name, err) + } +} \ No newline at end of file diff --git a/cmd/providers.go b/cmd/providers.go index ee0b7c6..a97121f 100644 --- a/cmd/providers.go +++ b/cmd/providers.go @@ -38,9 +38,9 @@ func init() { "Region in which the OneLogin API lives") cmdProvidersCreateOneLogin.Flags().IntVar(&providerDuration, "duration", 0, "(Optional) Default session duration in seconds") - cmdProvidersCreateOneLogin.MarkFlagRequired("client-id") - cmdProvidersCreateOneLogin.MarkFlagRequired("client-secret") - cmdProvidersCreateOneLogin.MarkFlagRequired("subdomain") + mandatoryFlag(cmdProvidersCreateOneLogin, "client-id") + mandatoryFlag(cmdProvidersCreateOneLogin, "client-secret") + mandatoryFlag(cmdProvidersCreateOneLogin, "subdomain") // Okta cmdProvidersCreateOkta.Flags().StringVar(&baseURL, "base-url", "", "Okta base URL") @@ -48,7 +48,7 @@ func init() { "Don't ask for a username and use this instead") cmdProvidersCreateOkta.Flags().IntVar(&providerDuration, "duration", 0, "(Optional) Default session duration in seconds") - cmdProvidersCreateOkta.MarkFlagRequired("base-url") + mandatoryFlag(cmdProvidersCreateOkta, "base-url") // Build command tree RootCmd.AddCommand(cmdProviders) diff --git a/cmd/root.go b/cmd/root.go index 4597ec4..fb5d2dc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -26,7 +26,10 @@ func init() { func Execute(version string) { VERSION = version - RootCmd.Execute() + err := RootCmd.Execute() + if err != nil { + log.Fatalf("Failed to execute: %v", err) + } } func initConfig() { diff --git a/cmd/status.go b/cmd/status.go index 909d030..72ec062 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -22,7 +22,10 @@ func init() { &readFromFile, "read-from-file", "r", "", "Read credentials from this file instead of the default ($HOME/.aws/credentials)", ) - viper.BindPFlag("global.credentials-path", cmdStatus.Flags().Lookup("read-from-file")) + err := viper.BindPFlag("global.credentials-path", cmdStatus.Flags().Lookup("read-from-file")) + if err != nil { + log.Fatalf(color.RedString("Error binding flag global.credentials-path: %v"), err) + } } var cmdStatus = &cobra.Command{ @@ -36,6 +39,10 @@ var cmdStatus = &cobra.Command{ func printStatus() { configfile, err := homedir.Expand(viper.GetString("global.credentials-path")) + if err != nil { + log.Fatalf(color.RedString("Failed to expand home: %s"), err) + } + profiles, err := aws.GetValidCredentials(configfile) if err != nil { log.Fatalf(color.RedString("Failed to retrieve non-expired credentials: %s"), err) @@ -51,7 +58,7 @@ func printStatus() { log.Print("The following apps currently have valid credentials:") for _, p := range profiles { - table.Append([]string{p.Name, fmt.Sprintf("%d", p.ExpireAtUnix), fmt.Sprintf("%s", p.LifetimeLeft.Round(time.Second))}) + table.Append([]string{p.Name, fmt.Sprintf("%d", p.ExpireAtUnix), p.LifetimeLeft.Round(time.Second).String()}) } table.Render()