Skip to content

Commit

Permalink
cmd/credentials.go: allow for persisting credentials
Browse files Browse the repository at this point in the history
Add a flag to the credentials command to save credentials to disk so
they can be reused on subsequent calls to the credentials command.

Change-Id: I779215c27f260eb241395fd5b68d9065b2a3b2e4
  • Loading branch information
dlamarmorgan committed Jul 27, 2023
1 parent 9a1c7e4 commit 9b4340e
Showing 1 changed file with 199 additions and 79 deletions.
278 changes: 199 additions & 79 deletions cmd/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ package cmd
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"strconv"
"strings"
"time"

"github.com/spf13/cobra"
Expand All @@ -20,35 +25,91 @@ import (
"storj.io/storj/satellite/console/consolewasm"
)

const (
password = "123a123"
secret = "Welcome1"
filename = ".creds"
)

var (
retry int
export bool
s3 bool
persist bool

satellite string
console string
authservice string

credentials Credentials
)

// Credentials is the structure of the credentials file.
type Credentials struct {
StorjUser string `json:"email"`
StorjPassword string `json:"password"`

ProjectID string `json:"ProjectID"`
Cookie string `json:"Cookie"`
ApiKey string `json:"ApiKey"`
EncryptionSecret string `json:"EncryptionSecret"`
Grant string `json:"Grant"`

AccessKey string `json:"AccessKey,omitempty"`
SecretKey string `json:"SecretKey,omitempty"`
Endpoint string `json:"Endpoint,omitempty"`
}

func credentialsCmd() *cobra.Command {
credentialsCmd := &cobra.Command{
Use: "credentials",
Short: "generate test user with credentialsCmd",
Short: "generate test user with credentials",
RunE: func(cmd *cobra.Command, args []string) error {
var err error
for i := -1; i < viper.GetInt("retry"); i++ {
err = addCredentials(context.Background())
if err == nil {
return nil
_, err := os.Stat(filename)
if err != nil || persist {
err = executeWithRetry(context.Background(), generateCredentials)
if err != nil {
return err
}
} else {
err = loadCredentials()
if err != nil {
return err
}
if !viper.GetBool("export") {
fmt.Println("#Server is not yet available. Retry in 1 sec...", err)
if s3 && (credentials.AccessKey == "" || credentials.SecretKey == "" || credentials.Endpoint == "") {
err = attemptUpdateDockerHost()
if err != nil {
return err
}
err = executeWithRetry(context.Background(), generateS3Credentials)
if err != nil {
return err
}
}
time.Sleep(1 * time.Second)
}
return err
err = printCredentials()
if err != nil {
return err
}
if persist {
err = persistCredentials()
if err != nil {
return err
}
}
return nil
},
}

pflags := credentialsCmd.PersistentFlags()
pflags.IntP("retry", "r", 300, "Number of retry with 1 second interval. Default 300 = 5 minutes.")
pflags.StringP("email", "m", "test@storj.io", "The email of the test user to use/create")
pflags.StringP("satellite", "s", "localhost:7777", "The host and port of of the satellite api to connect. Defaults to localhost or STORJ_DOCKER_HOST if set.")
pflags.StringP("console", "c", "localhost:10000", "The host and port of of the satellite api console to connect. Defaults to localhost or STORJ_DOCKER_HOST if set.")
pflags.StringP("authservice", "a", "http://localhost:8888", "Host of the auth service. Defaults to localhost or STORJ_DOCKER_HOST if set.")
pflags.BoolP("export", "e", false, "Turn it off to get bash compatible output with export statements.")
pflags.BoolP("write", "w", false, "DEPRECATED. Write the right entries to rclone config file (storjdev, storj)")
pflags.BoolP("s3", "", false, "Generate S3 credentials. IMPORTANT: this command MUST be executed INSIDE containers as gateway will use it.")
pflags.IntVarP(&retry, "retry", "r", 300, "Number of retry with 1 second interval. Default 300 = 5 minutes.")
pflags.StringVarP(&credentials.StorjUser, "email", "m", "test@storj.io", "The email of the test user to use/create")
pflags.StringVarP(&satellite, "satellite", "s", "localhost:7777", "The host and port of of the satellite api to connect. Defaults to localhost or STORJ_DOCKER_HOST if set.")
pflags.StringVarP(&console, "console", "c", "localhost:10000", "The host and port of of the satellite api console to connect. Defaults to localhost or STORJ_DOCKER_HOST if set.")
pflags.StringVarP(&authservice, "authservice", "a", "http://localhost:8888", "Host of the auth service. Defaults to localhost or STORJ_DOCKER_HOST if set.")
pflags.BoolVarP(&export, "export", "e", false, "Turn it off to get bash compatible output with export statements.")
pflags.BoolVarP(&s3, "s3", "", false, "Generate S3 credentials. IMPORTANT: this command MUST be executed INSIDE containers as gateway will use it.")
pflags.BoolVarP(&persist, "persist", "p", false, "Persist credentials to disk for reuse. If persisted credentials are found, they are returned instead of regenerating, however repeated calls with persist flag will regenerate and persist new credentials.")
pflags.VisitAll(func(flag *pflag.Flag) {
_ = viper.BindPFlag(flag.Name, flag)
})
Expand All @@ -60,106 +121,165 @@ func init() {
RootCmd.AddCommand(credentialsCmd())
}

func addCredentials(ctx context.Context) error {
func executeWithRetry(ctx context.Context, f func(ctx context.Context) error) error {
for i := 0; i < retry; i++ {
err := f(ctx)
if err == nil {
return nil
}
if !export {
fmt.Println("#Server is not yet available. Retry in 1 sec...", err)
}
time.Sleep(1 * time.Second)
}
return errors.New("Failed after " + strconv.Itoa(retry) + " retries")
}

satelliteAddress := viper.GetString("satellite")
consoleAddress := viper.GetString("console")
authService := viper.GetString("authservice")
func attemptUpdateDockerHost() error {
dockerHost := os.Getenv("STORJ_DOCKER_HOST")
if dockerHost != "" {
satelliteAddress = dockerHost + ":7777"
consoleAddress = dockerHost + ":10000"
authService = "http://" + dockerHost + ":8888"
satelliteUrl, err := url.Parse("http://" + satellite)
if err != nil {
return err
}
consoleUrl, err := url.Parse("http://" + console)
if err != nil {
return err
}
authUrl, err := url.Parse(authservice)
if err != nil {
return err
}
satellite = strings.Replace(satellite, satelliteUrl.Hostname(), dockerHost, 1)
console = strings.Replace(console, consoleUrl.Hostname(), dockerHost, 1)
authservice = strings.Replace(authservice, authUrl.Hostname(), dockerHost, 1)
}
return nil
}

func generateCredentials(ctx context.Context) error {
err := attemptUpdateDockerHost()
if err != nil {
return err
}
email := viper.GetString("email")
export := viper.GetBool("export")
s3 := viper.GetBool("s3")

ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
satelliteNodeURL, err := pkg.GetSatelliteID(ctx, satelliteAddress)

satelliteNodeURL, err := pkg.GetSatelliteID(ctx, satellite)
if err != nil {
return err
return errs.Wrap(err)
}
console := pkg.NewConsoleEndpoints(consoleAddress, email)

err = console.Login(ctx)
consoleEndpoint := pkg.NewConsoleEndpoints(console, credentials.StorjUser)
err = consoleEndpoint.Login(ctx)
if err != nil {
return err
return errs.Wrap(err)
}
projectID, cookie, err := console.GetOrCreateProject(ctx)

credentials.ProjectID, credentials.Cookie, err = consoleEndpoint.GetOrCreateProject(ctx)
if err != nil {
return errs.Wrap(err)
}

password := "123a123"
credentials.ApiKey, err = consoleEndpoint.CreateAPIKey(ctx, credentials.ProjectID)
if err != nil {
return errs.Wrap(err)
}

if !export {
fmt.Printf("User: %s\n", email)
fmt.Printf("Password: %s\n", password)
fmt.Printf("ProjectID: %s\n", projectID)
fmt.Printf("Cookie: _tokenKey=%s\n", cookie)
} else {
fmt.Printf("export STORJ_USER=%s\n", email)
fmt.Printf("export STORJ_USER_PASSWORD=%s\n", password)
fmt.Printf("export STORJ_PROJECT_ID=%s\n", projectID)
fmt.Printf("export STORJ_SESSION_COOKIE=Cookie: _tokenKey=%s\n", cookie)
projectUUID, err := uuid.FromString(credentials.ProjectID)
if err != nil {
return errs.Wrap(err)
}

apiKey, err := console.CreateAPIKey(ctx, projectID)
credentials.Grant, err = consolewasm.GenAccessGrant(satelliteNodeURL+"@"+satellite, credentials.ApiKey, secret, base64.StdEncoding.EncodeToString(projectUUID.Bytes()))
if err != nil {
return errs.Wrap(err)
}

secret := "Welcome1"
if s3 {
err = generateS3Credentials(ctx)
if err != nil {
return err
}
}

if !export {
fmt.Printf("API key: %s\n", apiKey)
fmt.Println()
} else {
fmt.Printf("export STORJ_API_KEY=%s\n", apiKey)
return err
}

func generateS3Credentials(ctx context.Context) error {
if _, err := os.Stat("docker-compose.yaml"); err == nil {
fmt.Println("Looks like you have a docker-compose.yaml. I suspect you execute this command from the host, not from the container. Please note that S3 compatible access Grant should use the container network host (satellite-api). Therefore it should be executed from the container. (docker-compose exec satellite-api storj-up credentials -s3)")
}
var err error
credentials.AccessKey, credentials.SecretKey, credentials.Endpoint, err = pkg.RegisterAccess(ctx, authservice, credentials.Grant)
if err != nil {
return errs.Wrap(err)
}
return err
}

projectUUID, err := uuid.FromString(projectID)
func persistCredentials() error {
credentials.StorjPassword = password
credentials.EncryptionSecret = secret
file, err := json.MarshalIndent(&credentials, "", " ")
if err != nil {
return errs.Wrap(err)
}
err = os.WriteFile(filename, file, 0644)
if err != nil {
return errs.Wrap(err)
}
return nil
}

grant, err := consolewasm.GenAccessGrant(satelliteNodeURL+"@"+satelliteAddress, apiKey, secret, base64.StdEncoding.EncodeToString(projectUUID.Bytes()))
func loadCredentials() error {
file, err := os.ReadFile(filename)
if err != nil {
return errs.Wrap(err)
}
err = json.Unmarshal(file, &credentials)
if err != nil {
return errs.Wrap(err)
}
return nil
}

func printCredentials() error {
if !export {
fmt.Printf("User: %s\n", credentials.StorjUser)
fmt.Printf("Password: %s\n", password)
fmt.Printf("ProjectID: %s\n", credentials.ProjectID)
fmt.Printf("Cookie: _tokenKey=%s\n", credentials.Cookie)

fmt.Printf("API key: %s\n", credentials.ApiKey)
fmt.Println()

fmt.Printf("Encryption secret: %s \n", secret)
fmt.Printf("Grant: %s\n", grant)
} else {
fmt.Printf("export STORJ_ENCRYPTION_SECRET=%s\n", secret)
fmt.Printf("export STORJ_ACCESS=%s\n", grant)
fmt.Printf("export UPLINK_ACCESS=%s\n", grant)
}
fmt.Printf("Grant: %s\n", credentials.Grant)

if s3 {
if _, err := os.Stat("docker-compose.yaml"); err == nil {
fmt.Println("Looks like you have a docker-compose.yaml. I suspect you execute this command from the host, not from the container. Please note that S3 compatible access grant should use the container network host (satellite-api). Therefore it should be executed from the container. (docker-compose exec satellite-api storj-up credentials -s3)")
}
accessKey, secretKey, endpoint, err := pkg.RegisterAccess(ctx, authService, grant)
if err != nil {
return errs.Wrap(err)
if s3 {
fmt.Printf("Access key: %s\n", credentials.AccessKey)
fmt.Printf("Secret key: %s\n", credentials.SecretKey)
fmt.Printf("Endpoint: %s\n", credentials.Endpoint)
}
if !export {
fmt.Printf("Access key: %s\n", accessKey)
fmt.Printf("Secret key: %s\n", secretKey)
fmt.Printf("Endpoint: %s\n", endpoint)
} else {
fmt.Printf("export AWS_ACCESS_KEY_ID=%s\n", accessKey)
fmt.Printf("export AWS_SECRET_ACCESS_KEY=%s\n", secretKey)
fmt.Printf("export STORJ_GATEWAY=%s\n", endpoint)
} else {
fmt.Printf("export STORJ_USER=%s\n", credentials.StorjUser)
fmt.Printf("export STORJ_USER_PASSWORD=%s\n", password)
fmt.Printf("export STORJ_PROJECT_ID=%s\n", credentials.ProjectID)
fmt.Printf("export STORJ_SESSION_COOKIE=Cookie: _tokenKey=%s\n", credentials.Cookie)

}
if viper.GetBool("write") {
fmt.Println("Write flag is removed. Rclone config examples are printed out by default.")
fmt.Printf("export STORJ_API_KEY=%s\n", credentials.ApiKey)

fmt.Printf("export STORJ_ENCRYPTION_SECRET=%s\n", secret)
fmt.Printf("export STORJ_ACCESS=%s\n", credentials.Grant)
fmt.Printf("export UPLINK_ACCESS=%s\n", credentials.Grant)

if s3 {
fmt.Printf("export AWS_ACCESS_KEY_ID=%s\n", credentials.AccessKey)
fmt.Printf("export AWS_SECRET_ACCESS_KEY=%s\n", credentials.SecretKey)
fmt.Printf("export STORJ_GATEWAY=%s\n", credentials.Endpoint)
}
}
return err
return nil
}

0 comments on commit 9b4340e

Please sign in to comment.