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
87 changes: 47 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,51 @@
<h1 align="center">
<img src="https://raw.githubusercontent.com/Permify/permify/master/assets/permify-logo.svg" alt="Permify logo" width="336px" /><br />
Permify CLI - Command Line Interface for Permify
</h1>

<p align="center">
<a href="https://github.com/Permify/permify-cli" target="_blank"><img src="https://img.shields.io/github/go-mod/go-version/Permify/permify-cli?style=for-the-badge&logo=go" alt="Permify CLI Go Version" /></a>&nbsp;
<a href="https://github.com/Permify/permify-cli" target="_blank"><img src="https://img.shields.io/github/license/Permify/permify-cli?style=for-the-badge" alt="Permify CLI Licence" /></a>&nbsp;
<a href="https://discord.gg/n6KfzYxhPp" target="_blank"><img src="https://img.shields.io/discord/950799928047833088?style=for-the-badge&logo=discord&label=DISCORD" alt="Permify Discord Channel" /></a>&nbsp;
</p>

## What is Permify?

[Permify](https://github.com/Permify/permify) is a open-source authorization service for creating and managing fine-grained permissions in your applications and services. Inspired by Google’s consistent, global authorization system, [Google Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf)

Our goal is to make Google's Zanzibar available to everyone and help them build robust, flexible, and easily auditable authorization systems that perform well in scaled environments.

### With Permify, you can:

🔮 Create permissions and policies using [Permify's flexible authorization language](https://docs.permify.co/docs/getting-started/modeling) that is compatible with traditional roles and permissions (RBAC), arbitrary relations between users and objects (ReBAC), and attributes (ABAC).

🔐 [Manage and store authorization data](https://docs.permify.co/docs/getting-started/sync-data) in your preferred database with high availability and consistency.

✅ [Interact with the Permify API](https://docs.permify.co/docs/getting-started/enforcement) to perform access checks, filter your resources with specific permissions, perform bulk permission checks for various resources, and more.
<h1 align="center">
<img src="https://raw.githubusercontent.com/Permify/permify/master/assets/permify-logo.svg" alt="Permify logo" width="336px" /><br />
Permify CLI - Command Line Interface for Permify
</h1>

<p align="center">
<a href="https://github.com/Permify/permify-cli" target="_blank"><img src="https://img.shields.io/github/go-mod/go-version/Permify/permify-cli?style=for-the-badge&logo=go" alt="Permify CLI Go Version" /></a>&nbsp;
<a href="https://github.com/Permify/permify-cli" target="_blank"><img src="https://img.shields.io/github/license/Permify/permify-cli?style=for-the-badge" alt="Permify CLI Licence" /></a>&nbsp;
<a href="https://discord.gg/n6KfzYxhPp" target="_blank"><img src="https://img.shields.io/discord/950799928047833088?style=for-the-badge&logo=discord&label=DISCORD" alt="Permify Discord Channel" /></a>&nbsp;
</p>

## What is Permify?

[Permify](https://github.com/Permify/permify) is a open-source authorization service for creating and managing fine-grained permissions in your applications and services. Inspired by Google’s consistent, global authorization system, [Google Zanzibar](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/41f08f03da59f5518802898f68730e247e23c331.pdf)

Our goal is to make Google's Zanzibar available to everyone and help them build robust, flexible, and easily auditable authorization systems that perform well in scaled environments.

### With Permify, you can:

🔮 Create permissions and policies using [Permify's flexible authorization language](https://docs.permify.co/docs/getting-started/modeling) that is compatible with traditional roles and permissions (RBAC), arbitrary relations between users and objects (ReBAC), and attributes (ABAC).

🔐 [Manage and store authorization data](https://docs.permify.co/docs/getting-started/sync-data) in your preferred database with high availability and consistency.

✅ [Interact with the Permify API](https://docs.permify.co/docs/getting-started/enforcement) to perform access checks, filter your resources with specific permissions, perform bulk permission checks for various resources, and more.

🧪 Test your authorization logic with [Permify's schema testing](https://docs.permify.co/docs/getting-started/testing). You can conduct scenario-based testing, policy coverage analysis, and IDL parser integration to achieve end-to-end validations for your desired authorization schema.

⚙️ Create custom and isolated authorization models for different applications using Permify [Multi-Tenancy](https://docs.permify.co/docs/use-cases/multi-tenancy) support, all managed within a single place, Permify instance.

## Communication Channels

🧪 Test your authorization logic with [Permify's schema testing](https://docs.permify.co/docs/getting-started/testing). You can conduct scenario-based testing, policy coverage analysis, and IDL parser integration to achieve end-to-end validations for your desired authorization schema.
## Configuration Credentials

⚙️ Create custom and isolated authorization models for different applications using Permify [Multi-Tenancy](https://docs.permify.co/docs/use-cases/multi-tenancy) support, all managed within a single place, Permify instance.

## Communication Channels
`permctl configure` stores the selected endpoint and tenant in the active
profile. It also supports optional `token`, `cert_path`, and `cert_key_path`
values so later client creation can reuse API credentials and TLS settings from
the same config file.

If you like Permify, please consider giving us a :star:

<p align="left">
<a href="https://discord.gg/n6KfzYxhPp">
<img height="70px" width="70px" alt="permify | Discord" src="https://user-images.githubusercontent.com/39353278/187209316-3d01a799-c51b-4eaa-8f52-168047078a14.png" />
</a>
<a href="https://twitter.com/GetPermify">
<img height="70px" width="70px" alt="permify | Twitter" src="https://user-images.githubusercontent.com/39353278/187209323-23f14261-d406-420d-80eb-1aa707a71043.png"/>
</a>
<a href="https://www.linkedin.com/company/permifyco">
<img height="70px" width="70px" alt="permify | Linkedin" src="https://user-images.githubusercontent.com/39353278/187209321-03293a24-6f63-4321-b362-b0fc89fdd879.png" />
</a>
</p>
<p align="left">
<a href="https://discord.gg/n6KfzYxhPp">
<img height="70px" width="70px" alt="permify | Discord" src="https://user-images.githubusercontent.com/39353278/187209316-3d01a799-c51b-4eaa-8f52-168047078a14.png" />
</a>
<a href="https://twitter.com/GetPermify">
<img height="70px" width="70px" alt="permify | Twitter" src="https://user-images.githubusercontent.com/39353278/187209323-23f14261-d406-420d-80eb-1aa707a71043.png"/>
</a>
<a href="https://www.linkedin.com/company/permifyco">
<img height="70px" width="70px" alt="permify | Linkedin" src="https://user-images.githubusercontent.com/39353278/187209321-03293a24-6f63-4321-b362-b0fc89fdd879.png" />
</a>
</p>
28 changes: 26 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 @@ -102,7 +103,31 @@ func runE(cmd *cobra.Command, _ []string) error {
return err
}

token, err := tui.StringPrompt("enter auth token (optional)", "", config.CliConfig.Token)
if err != nil {
return err
}

certPath, err := tui.StringPrompt("enter TLS cert path (optional)", "", config.CliConfig.CertPath)
if err != nil {
return err
}

certKeyPath, err := tui.StringPrompt("enter TLS cert key path (optional)", "", config.CliConfig.CertKeyPath)
if err != nil {
return err
}

config.CliConfig.PermifyURL = url
config.CliConfig.Token = token
config.CliConfig.CertPath = certPath
config.CliConfig.CertKeyPath = certKeyPath
config.CliConfig.SslEnabled = strings.HasPrefix(url, "https")

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

// Todo: Implement pagination
tenants, err := resp.Tenancy.List(context.Background(), &v1.TenantListRequest{})
Expand All @@ -117,12 +142,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
72 changes: 70 additions & 2 deletions core/client/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,87 @@
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) {
if endpoint == "" {
endpoint = config.CliConfig.PermifyURL
}
if endpoint == "" {
return nil, fmt.Errorf("permify endpoint is missing")
}

opts, err := dialOptions(config.CliConfig)
if err != nil {
return nil, err
}

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

func dialOptions(cfg config.CoreConfig) ([]grpc.DialOption, error) {
opts := []grpc.DialOption{}

transportCredentials, err := transportCredentials(cfg)
if err != nil {
return nil, err
}
opts = append(opts, grpc.WithTransportCredentials(transportCredentials))

if cfg.Token != "" {
token := cfg.Token
if !strings.HasPrefix(strings.ToLower(token), "bearer ") {
token = "Bearer " + token
}
if cfg.SslEnabled || cfg.CertPath != "" {
opts = append(opts, grpc.WithPerRPCCredentials(secureTokenCredentials{"authorization": token}))
} else {
opts = append(opts, grpc.WithPerRPCCredentials(nonSecureTokenCredentials{"authorization": token}))
}
}

return opts, nil
}

func transportCredentials(cfg config.CoreConfig) (credentials.TransportCredentials, error) {
if cfg.CertPath != "" && cfg.CertKeyPath != "" {
cert, err := tls.LoadX509KeyPair(cfg.CertPath, cfg.CertKeyPath)
if err != nil {
return nil, fmt.Errorf("failed to load TLS certificate pair: %w", err)
}
return credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
}), nil
}

if cfg.CertKeyPath != "" {
return nil, fmt.Errorf("cert_key_path requires cert_path")
}

if cfg.CertPath != "" {
return credentials.NewClientTLSFromFile(cfg.CertPath, "")
}

if cfg.SslEnabled {
return credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12}), nil
}

return insecure.NewCredentials(), nil
}
41 changes: 41 additions & 0 deletions core/client/grpc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package client

import (
"testing"

"github.com/Permify/permify-cli/core/config"
)

func TestDialOptionsAllowStoredTokenWithoutTLS(t *testing.T) {
opts, err := dialOptions(config.CoreConfig{
Token: "secret-token",
})
if err != nil {
t.Fatal(err)
}
if len(opts) != 2 {
t.Fatalf("expected transport and token options, got %d", len(opts))
}
}

func TestDialOptionsAllowStoredTokenWithTLS(t *testing.T) {
opts, err := dialOptions(config.CoreConfig{
SslEnabled: true,
Token: "secret-token",
})
if err != nil {
t.Fatal(err)
}
if len(opts) != 2 {
t.Fatalf("expected TLS transport and token options, got %d", len(opts))
}
}

func TestDialOptionsRejectCertKeyWithoutCert(t *testing.T) {
_, err := dialOptions(config.CoreConfig{
CertKeyPath: "/tmp/client.key",
})
if err == nil {
t.Fatal("expected cert_key_path without cert_path to fail")
}
}
9 changes: 6 additions & 3 deletions core/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,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"`
CertPath string `yaml:"cert_path,omitempty"`
CertKeyPath string `yaml:"cert_key_path,omitempty"`
SslEnabled bool `yaml:"-"`
}

// IsConfigured checks if permctl cli has been configured
Expand Down
51 changes: 51 additions & 0 deletions core/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package config

import (
"os"
"testing"
)

func TestConfigPersistsConnectionCredentials(t *testing.T) {
file, err := os.CreateTemp(t.TempDir(), "permctl-*.yaml")
if err != nil {
t.Fatal(err)
}
file.Close()

CliConfig = CoreConfig{
PermifyURL: "https://permify.example.com:3478",
Tenant: "tenant-1",
Token: "secret-token",
CertPath: "/tmp/client.crt",
CertKeyPath: "/tmp/client.key",
}

if err := New(file.Name(), "default"); err != nil {
t.Fatal(err)
}

CliConfig = CoreConfig{}

if err := Load(file.Name(), "default"); err != nil {
t.Fatal(err)
}

if CliConfig.PermifyURL != "https://permify.example.com:3478" {
t.Fatalf("unexpected endpoint: %q", CliConfig.PermifyURL)
}
if CliConfig.Tenant != "tenant-1" {
t.Fatalf("unexpected tenant: %q", CliConfig.Tenant)
}
if CliConfig.Token != "secret-token" {
t.Fatalf("unexpected token: %q", CliConfig.Token)
}
if CliConfig.CertPath != "/tmp/client.crt" {
t.Fatalf("unexpected cert path: %q", CliConfig.CertPath)
}
if CliConfig.CertKeyPath != "/tmp/client.key" {
t.Fatalf("unexpected cert key path: %q", CliConfig.CertKeyPath)
}
if !CliConfig.SslEnabled {
t.Fatal("expected https endpoint to enable TLS")
}
}