Skip to content

Commit

Permalink
feature(gateway): add route and chores for connector
Browse files Browse the repository at this point in the history
  • Loading branch information
henrybarreto committed Jun 11, 2024
1 parent 3d033bd commit 53b49f0
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 0 deletions.
10 changes: 10 additions & 0 deletions api/pkg/guard/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type AllActions struct {
Namespace NamespaceActions
Billing BillingActions
APIKey APIKeyActions
Connector ConnectorActions
}

type DeviceActions struct {
Expand Down Expand Up @@ -41,6 +42,10 @@ type APIKeyActions struct {
Create, Edit, Delete int
}

type ConnectorActions struct {
Update, Set, Status int
}

// Actions has all available and allowed actions.
// You should use it to get the code's action.
var Actions = AllActions{
Expand Down Expand Up @@ -99,4 +104,9 @@ var Actions = AllActions{
Edit: APIKeyEdit,
Delete: APIKeyDelete,
},
Connector: ConnectorActions{
Update: ConnectorUpdate,
Set: ConnectorSet,
Status: ConnectorStatus,
},
}
15 changes: 15 additions & 0 deletions api/pkg/guard/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ const (
APIKeyCreate
APIKeyEdit
APIKeyDelete

ConnectorUpdate
ConnectorSet
ConnectorStatus
ConnectorAction
)

var observerPermissions = Permissions{
Expand Down Expand Up @@ -128,6 +133,11 @@ var adminPermissions = Permissions{
APIKeyCreate,
APIKeyEdit,
APIKeyDelete,

ConnectorUpdate,
ConnectorSet,
ConnectorStatus,
ConnectorAction,
}

var ownerPermissions = Permissions{
Expand Down Expand Up @@ -185,4 +195,9 @@ var ownerPermissions = Permissions{
APIKeyCreate,
APIKeyEdit,
APIKeyDelete,

ConnectorUpdate,
ConnectorSet,
ConnectorStatus,
ConnectorAction,
}
18 changes: 18 additions & 0 deletions gateway/conf.d/shellhub.conf
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,24 @@ server {
}
{{ end -}}

{{ if bool (env.Getenv "SHELLHUB_CLOUD") -}}
location /api/connector {
set $upstream cloud-api:8080;
auth_request /auth;
auth_request_set $tenant_id $upstream_http_x_tenant_id;
auth_request_set $username $upstream_http_x_username;
auth_request_set $id $upstream_http_x_id;
auth_request_set $role $upstream_http_x_role;
error_page 500 =401 /auth;
rewrite ^/api/(.*)$ /api/$1 break;
proxy_set_header X-Tenant-ID $tenant_id;
proxy_set_header X-Username $username;
proxy_set_header X-ID $id;
proxy_set_header X-Role $role;
proxy_pass http://$upstream;
}
{{ end -}}

{{ if bool (env.Getenv "SHELLHUB_ENTERPRISE") -}}
location /api/firewall {
set $upstream cloud-api:8080;
Expand Down
36 changes: 36 additions & 0 deletions pkg/models/connector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package models

type ConnectorTLS struct {
// CA is the Certificate Authority used to generate the [Cert] for the server and the client.
CA string `json:"ca" bson:"ca" validate:"required,certPEM"`
// Cert is generated from [CA] certificate and used by the client to authorize the connection to the Docker Engine.
Cert string `json:"cert" bson:"cert" validate:"required,certPEM"`
// Key is the private key for the certificate on [Cert] field.
Key string `json:"key" bson:"key" validate:"required,privateKeyPEM"`
}

type Connector struct {
// UID is the unique identifier of Connector.
UID string `json:"uid" bson:"uid"`
// TenantID indicate which namespace this connector is related.
TenantID string `json:"tenant_id" bson:"tenant_id"`
// Enable indicates if the Connection's connection is enable.
Enable bool `json:"enable" bson:"enable"`
// Secure indicates if the Connector use HTTPS for authentication.
Secure bool `json:"secure" bson:"secure"`
// Address is the address with the port for the Docker Engine.
Address string `json:"address" bson:"address" validate:"required,hostname_port"`
// TLS stores the configuration for authenticate using TLS on the Docker Engine.
TLS *ConnectorTLS `json:"tls,omitempty" bson:"tls,omitempty"`
}

type ConnectorChanges struct {
// Enable indicates if the Connection's connection is enable.
Enable *bool `json:"enable"`
// Secure indicates if the Connector use HTTPS for authentication.
Secure *bool `json:"secure"`
// Address is the address with the port for the Docker Engine.
Address *string `json:"address" validate:"hostname_port"`
// TLS stores the configuration for authenticate using TLS on the Docker Engine.
TLS *ConnectorTLS `json:"tls"`
}
17 changes: 17 additions & 0 deletions pkg/validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const (
UserPasswordTag = "password"
// DeviceNameTag contains the rule to validate the device's name.
DeviceNameTag = "device_name"
// PrivateKeyPEMTag contains the rule to validate a private key.
PrivateKeyPEMTag = "privateKeyPEM"
CertPEMTag = "certPEM"
)

// Rules is a slice that contains all validation rules.
Expand Down Expand Up @@ -132,6 +135,20 @@ var Rules = []Rule{
},
Error: fmt.Errorf("role must be \"owner\", \"administrator\", \"operator\" or \"observer\""),
},
{
Tag: PrivateKeyPEMTag,
Handler: func(field validator.FieldLevel) bool {
return regexp.MustCompile(`^(-----BEGIN PRIVATE KEY-----(\n|\r|\r\n)([0-9a-zA-Z+/=]{64}(\n|\r|\r\n))*([0-9a-zA-Z+/=]{1,63}(\n|\r|\r\n))?-----END PRIVATE KEY-----)$`).MatchString(field.Field().String())
},
Error: fmt.Errorf("the priate key is invalid"),
},
{
Tag: CertPEMTag,
Handler: func(field validator.FieldLevel) bool {
return regexp.MustCompile(`^(-----BEGIN CERTIFICATE-----(\n|\r|\r\n)([0-9a-zA-Z+/=]{64}(\n|\r|\r\n))*([0-9a-zA-Z+/=]{1,63}(\n|\r|\r\n))?-----END CERTIFICATE-----)$`).MatchString(field.Field().String())
},
Error: fmt.Errorf("the cert is invalid"),
},
}

// Validator is the ShellHub validator.
Expand Down
196 changes: 196 additions & 0 deletions pkg/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,199 @@ func TestDeviceName(t *testing.T) {
})
}
}

func TestKeyPEM(t *testing.T) {
tests := []struct {
description string
value string
want bool
}{
{
description: "failed when the private key is empty",
value: "",
want: false,
},
{
description: "failed when the private key does not have the header",
value: `
MC4CAQAwBQYDK2VwBCIEIA2Ecxi0E2XsKUNRYBv98VRbpsjl/kD7l7XOa/aKYitU
-----END PRIVATE KEY-----`,
want: false,
},
{
description: "failed when the private key does not have the footer",
value: `-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIA2Ecxi0E2XsKUNRYBv98VRbpsjl/kD7l7XOa/aKYitU
`,
want: false,
},
{
description: "failed when the private key does not have header neither footer",
value: `
MC4CAQAwBQYDK2VwBCIEIA2Ecxi0E2XsKUNRYBv98VRbpsjl/kD7l7XOa/aKYitU
`,
want: false,
},
{
description: "success when the private key is a valid ED25519",
value: `-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIA2Ecxi0E2XsKUNRYBv98VRbpsjl/kD7l7XOa/aKYitU
-----END PRIVATE KEY-----`,
want: true,
},
{
description: "success when the private key is a valid RSA4096",
value: `-----BEGIN PRIVATE KEY-----
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
rRrVfCKhbJKpjAZq5U9VKt9LcGe9kA==
-----END PRIVATE KEY-----`,
want: true,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
data := struct {
Key string `validate:"required,privateKeyPEM"`
}{
Key: tt.value,
}

ok, _ := New().Struct(data)

assert.Equal(t, tt.want, ok)
})
}
}

func TestCertPEM(t *testing.T) {
tests := []struct {
description string
value string
want bool
}{
{
description: "failed when the cert is empty",
value: "",
want: false,
},
{
description: "failed when the cert does not have the header",
value: `
MC4CAQAwBQYDK2VwBCIEIA2Ecxi0E2XsKUNRYBv98VRbpsjl/kD7l7XOa/aKYitU
-----END CERTIFICATE-----`,
want: false,
},
{
description: "failed when the cert does not have the footer",
value: `-----BEGIN CERTIFICATE-----
MC4CAQAwBQYDK2VwBCIEIA2Ecxi0E2XsKUNRYBv98VRbpsjl/kD7l7XOa/aKYitU
`,
want: false,
},
{
description: "failed when the cert does not have header neither footer",
value: `
MC4CAQAwBQYDK2VwBCIEIA2Ecxi0E2XsKUNRYBv98VRbpsjl/kD7l7XOa/aKYitU
`,
want: false,
},
{
description: "success when the cert is a valid",
value: `-----BEGIN CERTIFICATE-----
MIIFUTCCAzmgAwIBAgIUGOBHWPTiCbwt8iLWYNZwKTDbONUwDQYJKoZIhvcNAQEL
BQAwWzELMAkGA1UEBhMCQlIxDjAMBgNVBAgMBUJhaGlhMRQwEgYDVQQHDAtYaXF1
ZS1YaXF1ZTEQMA4GA1UECgwHSGVucnknczEUMBIGA1UEAwwLZGVsbGcxNTU1MTAw
HhcNMjQwNTI5MTk1NzA0WhcNMjUwNTI5MTk1NzA0WjARMQ8wDQYDVQQDDAZjbGll
bnQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDt92hrf1PDvCAwNaEv
1xjfL2QCyEsA7zxBGPIIA5ETsB41LW3yS98oy8F/L72BDEepmsw49DaQLbIZJrjX
yT4dtYKN9oPgv5uwwmwPrWexsiDiVA968DOgSWj4S4MIDLAwd3gBqrQgqFutIwgt
58KzhKYn/J9+1q/G8ecKzRre7c7/PQbCHEH4A/XiIudyuSf49ziU+U7dq9rZIAiy
G2xMAKZnjANP0dQj8gaAJCD1qesyoIUXrHCuesrZEEY1gov6ZxUeR62KQgIFJDQ8
nrGgPRc/AjNcLhLKH5xaRqfbEv3WyYw1Ag4Fc1ZtIOgLbMr9BRcxnrhCAIBD4ASU
+63N5zxC/K0JOPy4iSa8+uMXoYD4eJIUI4e9cuAp976zCsrd6d2QEDZmly2/KGrc
TunlNQ49LfqV9LQWnumRoQ5vhlOHWAQmY48svf45PGeQrrbLUfV24uO4ZzwnCCCH
BUUUwTlasZi1zwHgZ1rmqOjemnGn6HJ9T64tFypUQKOiS5NxeAajszQLf3GfIE8Z
ibE+uxZQyvRexmyUt+RaOQfyAKtnczyOd9LU4/JqVtbKYtuxltw503gS+RuzxcHu
FEv/takSszbr9mKAj/pT0MEKE9nJLP2gcqw0j2fdjfWWejPGwWlxJ98sPlw8eh4K
NOtphFmgbjIUTrjfS6G+3cbOwQIDAQABo1cwVTATBgNVHSUEDDAKBggrBgEFBQcD
AjAdBgNVHQ4EFgQUzvw/tD0WsD5q2K2wSokjLEReY6wwHwYDVR0jBBgwFoAU9Nw4
MqfdGEeRWXI2H1ChuK2k9qEwDQYJKoZIhvcNAQELBQADggIBAIQp2CQyPjaqbXZc
hiR0VWwAyifttrHJJ59VCFovH4/LW8oPbg8w7JP4bfm9iTbo7yTqDV6BfOWat4Qf
T5o0HVcmxKEY7X6bEAmTFfSsNs6NTuaIE8QSFpJpKvLGIjulSqhayjSPuqJavluc
lGa1vUPeIqZAKPDFwrdqMXg/Q7DMhg9su7QPfNVu2E2Hrq++PaXPnWZlu3/yu5FH
2qjoS/xeG8QL8STzqVxqsmcGXkI8FYT2Goidb5eNPSqJflntgm0FzZ/YYvCpZbdC
8/Qjg+CnopfuyLS72iZvW4tSv/9plBsiu6UqhbjBz9xQZbBDpvUOyUvK+L8URmWB
21xTMtqdqk3iG3qAFGnaz0EM0Tg4MEopzYMieob2XoxjSH55ykj33LF/sZeNVPzK
gXi2bqLzL5I1kTPF+Irrg5z7FBTcXRVdPcvqjxGfbyVVmaxNmC26ozIF94rYUOIr
JeUB+pKG1xX/fhUAMeLvEkJ6GOl6ldnTqPJrNAZzwAqW5ra0H9kIbmf1fGPpezaa
KdtGUV3wYjChWAuSa0S3mP1qD+sRNS5NtR7efemmoUbR+hCg2Vyo5osRSJ9dkQJf
PNcoe7LEpZdYQvPI5v1fqVcFpOdOCckDdaGb3XPpd69LGdFD0jHOzF9eIavv9ewV
eiDIAGdPArZi+JWdNsp+TK4MJjcy
-----END CERTIFICATE-----`,
want: true,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
data := struct {
Cert string `validate:"required,certPEM"`
}{
Cert: tt.value,
}

ok, _ := New().Struct(data)

assert.Equal(t, tt.want, ok)
})
}
}

0 comments on commit 53b49f0

Please sign in to comment.