Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
25 changes: 23 additions & 2 deletions core/cli/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"strings"

"github.com/Permify/permify-cli/core/client"
"github.com/Permify/permify-cli/core/config"
Expand Down Expand Up @@ -101,8 +102,29 @@ func runE(cmd *cobra.Command, _ []string) error {
if err != nil {
return err
}
token, err := tui.StringPrompt("enter token (optional)", "", config.CliConfig.Token)
if err != nil {
return err
}
certificatePath, err := tui.StringPrompt("enter certificate path (optional)", "", config.CliConfig.CertificatePath)
if err != nil {
return err
}
certificateKeyPath, err := tui.StringPrompt("enter certificate key path (optional)", "", config.CliConfig.CertificateKeyPath)
if err != nil {
return err
}

config.CliConfig.PermifyURL = url
config.CliConfig.Token = token
config.CliConfig.CertificatePath = certificatePath
config.CliConfig.CertificateKeyPath = certificateKeyPath
config.CliConfig.SslEnabled = strings.HasPrefix(config.CliConfig.PermifyURL, "https")

resp, err := client.New(url)
if err != nil {
logger.Log.Fatal(err)
}

// Todo: Implement pagination
tenants, err := resp.Tenancy.List(context.Background(), &v1.TenantListRequest{})
Expand All @@ -117,12 +139,11 @@ func runE(cmd *cobra.Command, _ []string) error {
tenantNames = append(tenantNames, nameID)
tenantIds[nameID] = tenant.Id
}

tenant, err := tui.Choice("Select a tenant: ", tenantNames)
if err != nil {
logger.Log.Error(err)
}
config.CliConfig.PermifyURL = url
config.CliConfig.Tenant = tenantIds[tenant]
err = config.Write()
if err != nil {
Expand Down
53 changes: 50 additions & 3 deletions core/client/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,66 @@
package client

import (
"crypto/tls"
"fmt"
"strings"

"github.com/Permify/permify-cli/core/config"
permify "github.com/Permify/permify-go/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)

// New initializes a new permify client
func New(endpoint string) (*permify.Client, error) {
clientConfig := config.CliConfig
if endpoint != "" {
clientConfig.PermifyURL = endpoint
clientConfig.SslEnabled = strings.HasPrefix(endpoint, "https")
}

options := []grpc.DialOption{}
if clientConfig.SslEnabled {
transportCredentials, err := tlsCredentials(clientConfig)
if err != nil {
return nil, err
}
options = append(options, grpc.WithTransportCredentials(transportCredentials))
} else {
options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials()))
}

if clientConfig.Token != "" {
token := map[string]string{"authorization": fmt.Sprintf("Bearer %s", clientConfig.Token)}
if clientConfig.SslEnabled {
options = append(options, grpc.WithPerRPCCredentials(secureTokenCredentials(token)))
} else {
options = append(options, grpc.WithPerRPCCredentials(nonSecureTokenCredentials(token)))
}
}

client, err := permify.NewClient(
permify.Config{
Endpoint: endpoint,
Endpoint: clientConfig.PermifyURL,
},
// Todo: Implement secure call with tls certificate
grpc.WithTransportCredentials(insecure.NewCredentials()),
options...,
)
return client, err
}

func tlsCredentials(clientConfig config.CoreConfig) (credentials.TransportCredentials, error) {
tlsConfig := &tls.Config{}
if clientConfig.CertificatePath == "" && clientConfig.CertificateKeyPath == "" {
return credentials.NewTLS(tlsConfig), nil
}
if clientConfig.CertificatePath == "" || clientConfig.CertificateKeyPath == "" {
return nil, fmt.Errorf("both certificate_path and certificate_key_path are required when configuring client certificates")
}
cert, err := tls.LoadX509KeyPair(clientConfig.CertificatePath, clientConfig.CertificateKeyPath)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
return credentials.NewTLS(tlsConfig), nil
}
93 changes: 87 additions & 6 deletions core/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"

"github.com/Permify/permify-cli/core/logger"
"gopkg.in/yaml.v3"
)

const credentialsDir = ".permify"
const credentialsFileName = "credentials"

// CliConfig is the global config variable
var CliConfig = CoreConfig{}

Expand All @@ -25,9 +29,12 @@ type ProfileConfigs struct {

// CoreConfig is the config struct
type CoreConfig struct {
PermifyURL string `yaml:"permify_url"`
Tenant string `yaml:"tenant"`
SslEnabled bool `yaml:"-"`
PermifyURL string `yaml:"permify_url"`
Tenant string `yaml:"tenant"`
Token string `yaml:"token,omitempty"`
CertificatePath string `yaml:"certificate_path,omitempty"`
CertificateKeyPath string `yaml:"certificate_key_path,omitempty"`
SslEnabled bool `yaml:"-"`
}

// IsConfigured checks if permctl cli has been configured
Expand Down Expand Up @@ -70,6 +77,9 @@ func Load(file string, profile string) error {
profileConfigs.File = file
profileConfigs.Profile = profile
CliConfig = profileConfigs.Configs[profile]
if err := loadCredentials(profile); err != nil {
return err
}
CliConfig.SslEnabled = strings.HasPrefix(CliConfig.PermifyURL, "https")
return err
}
Expand All @@ -79,7 +89,7 @@ func New(file string, profile string) error {
profileConfigs.Profile = profile
profileConfigs.File = file
profileConfigs.Configs = make(map[string]CoreConfig)
profileConfigs.Configs[profile] = CliConfig
profileConfigs.Configs[profile] = configWithoutCredentials(CliConfig)
newConfigDataByte, err := yaml.Marshal(profileConfigs.Configs)
if err != nil {
return err
Expand All @@ -95,11 +105,82 @@ func Write() error {
return fmt.Errorf("%s config file does not exist", profileConfigs.File)
}
profile := profileConfigs.Profile
profileConfigs.Configs[profile] = CliConfig
profileConfigs.Configs[profile] = configWithoutCredentials(CliConfig)
newConfigDataByte, err := yaml.Marshal(profileConfigs.Configs)
if err != nil {
return err
}
err = os.WriteFile(profileConfigs.File, newConfigDataByte, fs.FileMode(0644))
return err
if err != nil {
return err
}
return writeCredentials(profile)
}

func configWithoutCredentials(config CoreConfig) CoreConfig {
config.Token = ""
config.CertificatePath = ""
config.CertificateKeyPath = ""
return config
}

func credentialsPath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, credentialsDir, credentialsFileName), nil
}

func loadCredentials(profile string) error {
file, err := credentialsPath()
if err != nil {
return err
}
data, err := os.ReadFile(file)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
credentials := map[string]CoreConfig{}
if err := yaml.Unmarshal(data, &credentials); err != nil {
return err
}
profileCredentials := credentials[profile]
CliConfig.Token = profileCredentials.Token
CliConfig.CertificatePath = profileCredentials.CertificatePath
CliConfig.CertificateKeyPath = profileCredentials.CertificateKeyPath
return nil
}

func writeCredentials(profile string) error {
file, err := credentialsPath()
if err != nil {
return err
}
credentials := map[string]CoreConfig{}
data, err := os.ReadFile(file)
if err != nil && !os.IsNotExist(err) {
return err
}
if len(data) > 0 {
if err := yaml.Unmarshal(data, &credentials); err != nil {
return err
}
}
credentials[profile] = CoreConfig{
Token: CliConfig.Token,
CertificatePath: CliConfig.CertificatePath,
CertificateKeyPath: CliConfig.CertificateKeyPath,
}
newCredentialData, err := yaml.Marshal(credentials)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(file), fs.FileMode(0700)); err != nil {
return err
}
return os.WriteFile(file, newCredentialData, fs.FileMode(0600))
}