Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth command #253

Merged
merged 16 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/debricked/cli
go 1.20

require (
github.com/becheran/wildmatch-go v1.0.0
github.com/bmatcuk/doublestar/v4 v4.6.0
github.com/chelnak/ysmrr v0.2.1
github.com/fatih/color v1.16.0
Expand All @@ -15,22 +16,28 @@ require (
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.4
github.com/vifraa/gopom v0.2.1
golang.org/x/oauth2 v0.22.0
golang.org/x/tools v0.19.0
gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.2.1
github.com/becheran/wildmatch-go v1.0.0
)

require (
dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/cli/browser v1.0.0 // indirect
github.com/cli/safeexec v1.0.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-oauth2/oauth2 v3.9.2+incompatible // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
Expand All @@ -57,6 +64,7 @@ require (
github.com/stretchr/objx v0.5.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/zalando/go-keyring v0.2.5 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/net v0.22.0 // indirect
Expand Down
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA=
Expand All @@ -58,6 +60,12 @@ github.com/chelnak/ysmrr v0.2.1/go.mod h1:9TEgLy2xDMGN62zJm9XZrEWY/fHoGoBslSVEkE
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cli/browser v1.0.0 h1:RIleZgXrhdiCVgFBSjtWwkLPUCWyhhhN5k5HGSBt1js=
github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q=
github.com/cli/oauth v1.0.1 h1:pXnTFl/qUegXHK531Dv0LNjW4mLx626eS42gnzfXJPA=
github.com/cli/oauth v1.0.1/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4=
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
Expand All @@ -69,6 +77,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -99,6 +109,10 @@ github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lK
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-oauth2/oauth2 v3.9.2+incompatible h1:A8gSjq4110EgZDVk4ZtcpusynU2Fto9eM6sXvxL+EOs=
github.com/go-oauth2/oauth2 v3.9.2+incompatible/go.mod h1:GGcZ+i513KxN4yS7zBYfmwo3P+cyGvCS675uCNmWv/g=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -272,6 +286,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8=
github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
Expand Down Expand Up @@ -375,6 +391,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
39 changes: 39 additions & 0 deletions internal/cmd/login/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package login

import (
"fmt"
"github.com/debricked/cli/internal/login"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

func NewLoginCmd(authenticator login.IAuthenticator) *cobra.Command {
cmd := &cobra.Command{
Use: "login",
Short: "Authenticate debricked user",
Long: `Start authentication flow to generate access token.`,
PreRun: func(cmd *cobra.Command, _ []string) {
_ = viper.BindPFlags(cmd.Flags())
},
RunE: RunE(authenticator),
}

return cmd
}

func RunE(a login.IAuthenticator) func(_ *cobra.Command, args []string) error {
return func(cmd *cobra.Command, _ []string) error {
token, err := login.AuthToken()
if err != nil {
return err
}
fmt.Printf(
"%s Successfully authenticated\nToken=%s",
color.GreenString("✔"),
color.BlueString(token),
)

return nil
}
}
2 changes: 2 additions & 0 deletions internal/cmd/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/debricked/cli/internal/cmd/callgraph"
"github.com/debricked/cli/internal/cmd/files"
"github.com/debricked/cli/internal/cmd/fingerprint"
"github.com/debricked/cli/internal/cmd/login"
"github.com/debricked/cli/internal/cmd/report"
"github.com/debricked/cli/internal/cmd/resolve"
"github.com/debricked/cli/internal/cmd/scan"
Expand Down Expand Up @@ -47,6 +48,7 @@ Read more: https://docs.debricked.com/product/administration/generate-access-tok
rootCmd.AddCommand(fingerprint.NewFingerprintCmd(container.Fingerprinter()))
rootCmd.AddCommand(resolve.NewResolveCmd(container.Resolver()))
rootCmd.AddCommand(callgraph.NewCallgraphCmd(container.CallgraphGenerator()))
rootCmd.AddCommand(login.NewLoginCmd(container.Authenticator()))

rootCmd.CompletionOptions.DisableDefaultCmd = true

Expand Down
141 changes: 141 additions & 0 deletions internal/login/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package login

import (
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
// "github.com/debricked/cli/internal/client"
"log"
"math/rand"
"net/http"
"os/exec"
"runtime"
"strings"
"time"

"golang.org/x/oauth2"
)

type IAuthenticator interface {
Authenticate() (*oauth2.Token, error)
}

type Authenticator struct {
ClientID string
Scopes []string
}

const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

func generateRandomString(length int) string {
seed := rand.NewSource(time.Now().UnixNano())
r := rand.New(seed)

b := make([]byte, length)
for i := range b {
b[i] = charset[r.Intn(len(charset))]
}
return string(b)
}

func createCodeChallenge(codeVerifier string) string {
// Create a SHA-256 hash of the code verifier
hash := sha256.Sum256([]byte(codeVerifier))

// Encode the hash to base64
encoded := base64.StdEncoding.EncodeToString(hash[:])

// Make it URL safe
encoded = strings.TrimRight(encoded, "=")
encoded = strings.ReplaceAll(encoded, "+", "-")
encoded = strings.ReplaceAll(encoded, "/", "_")

return encoded
}

filip-debricked marked this conversation as resolved.
Show resolved Hide resolved
func (a Authenticator) Authenticate() (*oauth2.Token, error) {
// Set up OAuth2 configuration
config := &oauth2.Config{
ClientID: a.ClientID,
ClientSecret: "",
Endpoint: oauth2.Endpoint{
AuthURL: "https://debricked.com/app/oauth/authorize",
TokenURL: "https://debricked.com/app/oauth/token",
},
RedirectURL: "http://localhost:9096/callback",
Scopes: a.Scopes,
}

// Create a random state
state := generateRandomString(8)
codeVerifier := generateRandomString(64)
filip-debricked marked this conversation as resolved.
Show resolved Hide resolved

// Generate the authorization URL
authURL := config.AuthCodeURL(
state,
oauth2.SetAuthURLParam("code_challenge", createCodeChallenge(codeVerifier)),
oauth2.SetAuthURLParam("code_challenge_method", "S256"),
filip-debricked marked this conversation as resolved.
Show resolved Hide resolved
)

// Start a temporary HTTP server to handle the callback
code := make(chan string)
defer close(code)
server := &http.Server{Addr: ":9096"}
// Start the server in a goroutine
go func() {
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("HTTP server error: %v", err)
}
}()

// Ensure the server is shut down when we're done
defer server.Shutdown(context.Background())

// Open the browser for the user to log in
err := openBrowser(authURL)
if err != nil {
log.Fatal("Could not open browser:", err)
}

http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("state") != state {
http.Error(w, "Invalid state", http.StatusBadRequest)
return
}

code <- r.URL.Query().Get("code")
fmt.Fprintf(w, "Authentication successful! You can close this window now.")
})
// Wait for the authorization code
authCode := <-code

// Exchange the authorization code for a token
token, err := config.Exchange(
context.Background(),
authCode,
oauth2.SetAuthURLParam("client_id", a.ClientID),
oauth2.SetAuthURLParam("code_verifier", codeVerifier),
filip-debricked marked this conversation as resolved.
Show resolved Hide resolved
)
if err != nil {
return nil, err
}
return token, nil
}

func openBrowser(url string) error {
var cmd string
var args []string

switch runtime.GOOS {
case "windows":
cmd = "cmd"
args = []string{"/c", "start"}
case "darwin":
cmd = "open"
default: // "linux", "freebsd", "openbsd", "netbsd"
cmd = "xdg-open"
}
args = append(args, url)
return exec.Command(cmd, args...).Start()
}
10 changes: 10 additions & 0 deletions internal/login/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package login

func AuthToken() (string, error) {
tokenSource := GetDebrickedTokenSource()
token, err := tokenSource.Token()
if err != nil {
return "", err
}
return token.AccessToken, nil
}
67 changes: 67 additions & 0 deletions internal/login/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package login

import (
"github.com/zalando/go-keyring"

"golang.org/x/oauth2"
)

type SecretClient interface {
Set(string, string) error
Get(string) (string, error)
}

type DebrickedSecretClient struct {
User string
}

type DebrickedTokenSource struct {
SecretClient SecretClient
}

func (dsc DebrickedSecretClient) Set(service, secret string) error {
return keyring.Set(service, dsc.User, secret)
}

func (dsc DebrickedSecretClient) Get(service string) (string, error) {
return keyring.Get(service, dsc.User)
}

func GetDebrickedTokenSource() oauth2.TokenSource {
return DebrickedTokenSource{
SecretClient: DebrickedSecretClient{
User: "DebrickedCLI",
},
}
}

func (dts DebrickedTokenSource) Token() (*oauth2.Token, error) {
refreshToken, err := dts.SecretClient.Get("DebrickedRefreshToken")
if err != nil {
if err == keyring.ErrNotFound {
// refreshToken is not yet set, initialize authorization
authenticator := Authenticator{
ClientID: "01919462-7d6e-78e8-aa24-ba779213c90f",
Scopes: []string{"select", "profile", "basicRepo"},
}
token, err := authenticator.Authenticate()
if err != nil {
return nil, err
}
dts.SecretClient.Set("DebrickedRefreshToken", token.RefreshToken)
dts.SecretClient.Set("DebrickedAccessToken", token.AccessToken)
} else {
return nil, err
}
}
accessToken, err := dts.SecretClient.Get("DebrickedAccessToken")
if err != nil {
accessToken = ""
}
// TODO: Parse expiry date
return &oauth2.Token{
RefreshToken: refreshToken,
TokenType: "jwt",
AccessToken: accessToken,
}, nil
}
Loading
Loading