diff --git a/cmd/permify/main.go b/cmd/permify/main.go new file mode 100644 index 000000000..20f533e8d --- /dev/null +++ b/cmd/permify/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "os" + + "github.com/cespare/xxhash/v2" + "github.com/sercand/kuberesolver/v5" + "google.golang.org/grpc/balancer" + + consistentbalancer "github.com/Permify/permify/pkg/balancer" + "github.com/Permify/permify/pkg/cmd" +) + +func main() { + kuberesolver.RegisterInCluster() + balancer.Register(consistentbalancer.NewBuilder(xxhash.Sum64)) + + root := cmd.NewRootCommand() + root.AddCommand(cmd.NewServeCommand()) + root.AddCommand(cmd.NewValidateCommand()) + root.AddCommand(cmd.NewCoverageCommand()) + root.AddCommand(cmd.NewGenerateAstCommand()) + root.AddCommand(cmd.NewMigrateCommand()) + root.AddCommand(cmd.NewVersionCommand()) + root.AddCommand(cmd.NewConfigCommand()) + root.AddCommand(cmd.NewRepairCommand()) + + // Add configure command + root.AddCommand(cmd.NewConfigureCommand()) + + if err := root.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/cmd/permify/permify.go b/cmd/permify/permify.go deleted file mode 100644 index 4dfd520b0..000000000 --- a/cmd/permify/permify.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "os" - - "github.com/cespare/xxhash/v2" - - "github.com/sercand/kuberesolver/v5" - "google.golang.org/grpc/balancer" - - consistentbalancer "github.com/Permify/permify/pkg/balancer" - "github.com/Permify/permify/pkg/cmd" -) - -func main() { - kuberesolver.RegisterInCluster() - balancer.Register(consistentbalancer.NewBuilder(xxhash.Sum64)) - - // Setup CLI commands - root := cmd.NewRootCommand() - - // Add serve command - serve := cmd.NewServeCommand() - root.AddCommand(serve) - - // Add validate command - validate := cmd.NewValidateCommand() - root.AddCommand(validate) - - // Add coverage command - coverage := cmd.NewCoverageCommand() - root.AddCommand(coverage) - - // Add AST generation command - ast := cmd.NewGenerateAstCommand() - root.AddCommand(ast) - - // Add migrate command - migrate := cmd.NewMigrateCommand() - root.AddCommand(migrate) - - // Add version command - version := cmd.NewVersionCommand() - root.AddCommand(version) - - // Add config command - config := cmd.NewConfigCommand() - root.AddCommand(config) - - // Add repair command - repair := cmd.NewRepairCommand() - root.AddCommand(repair) - - if err := root.Execute(); err != nil { - os.Exit(1) - } -} diff --git a/permify b/permify new file mode 100755 index 000000000..0814dba32 Binary files /dev/null and b/permify differ diff --git a/pkg/cmd/configure.go b/pkg/cmd/configure.go new file mode 100644 index 000000000..6bd9c52e9 --- /dev/null +++ b/pkg/cmd/configure.go @@ -0,0 +1,139 @@ +package cmd + +import ( + "bufio" + "context" + "crypto/tls" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "gopkg.in/yaml.v3" +) + +type Credentials struct { + Endpoint string `yaml:"endpoint"` + APIToken string `yaml:"api_token"` + CertPath string `yaml:"cert_path"` + CertKey string `yaml:"cert_key"` +} + +func getCredentialsPath() string { + home, err := os.UserHomeDir() + if err != nil { + fmt.Println("Error getting home directory:", err) + os.Exit(1) + } + return filepath.Join(home, ".permify", "credentials") +} + +func NewConfigureCommand() *cobra.Command { + return &cobra.Command{ + Use: "configure", + Short: "Configure Permify CLI credentials", + Run: func(cmd *cobra.Command, args []string) { + reader := bufio.NewReader(os.Stdin) + + fmt.Print("Endpoint (e.g., localhost:3478): ") + endpoint, _ := reader.ReadString('\n') + + fmt.Print("API Token: ") + apiToken, _ := reader.ReadString('\n') + + fmt.Print("Cert Path (optional): ") + certPath, _ := reader.ReadString('\n') + + fmt.Print("Cert Key (optional): ") + certKey, _ := reader.ReadString('\n') + + creds := Credentials{ + Endpoint: strings.TrimSpace(endpoint), + APIToken: strings.TrimSpace(apiToken), + CertPath: strings.TrimSpace(certPath), + CertKey: strings.TrimSpace(certKey), + } + + data, err := yaml.Marshal(&creds) + if err != nil { + fmt.Println("Error marshaling credentials:", err) + os.Exit(1) + } + + credsPath := getCredentialsPath() + credsDir := filepath.Dir(credsPath) + + if err := os.MkdirAll(credsDir, 0700); err != nil { + fmt.Println("Error creating credentials directory:", err) + os.Exit(1) + } + + if err := os.WriteFile(credsPath, data, 0600); err != nil { + fmt.Println("Error writing credentials file:", err) + os.Exit(1) + } + + fmt.Println("Credentials saved successfully in ~/.permify/credentials") + }, + } +} + +type TokenAuth struct { + Token string +} + +func (t TokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + return map[string]string{ + "authorization": "Bearer " + t.Token, + }, nil +} + +// FIX: Blindamos el envío del token (Security Risk) +func (TokenAuth) RequireTransportSecurity() bool { + return true +} + +func ConfiguredGRPCClient() ([]grpc.DialOption, string, error) { + credsPath := getCredentialsPath() + data, err := os.ReadFile(credsPath) + if err != nil { + return nil, "", fmt.Errorf("could not read credentials (run 'permify configure' first): %w", err) + } + + var creds Credentials + if err := yaml.Unmarshal(data, &creds); err != nil { + return nil, "", fmt.Errorf("could not parse credentials: %w", err) + } + + var opts []grpc.DialOption + + if creds.APIToken != "" { + opts = append(opts, grpc.WithPerRPCCredentials(TokenAuth{Token: creds.APIToken})) + } + + if creds.CertPath != "" && creds.CertKey != "" { + cert, err := tls.LoadX509KeyPair(creds.CertPath, creds.CertKey) + if err != nil { + return nil, "", fmt.Errorf("could not load client key pair: %w", err) + } + // FIX: Forzamos la versión mínima de TLS + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + MinVersion: tls.VersionTLS12, + } + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) + } else { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } + + endpoint := creds.Endpoint + if endpoint == "" { + endpoint = "localhost:3478" + } + + return opts, endpoint, nil +}