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 26, 2024
1 parent a9fa47f commit 54df9ee
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 0 deletions.
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
6 changes: 6 additions & 0 deletions pkg/api/authorizer/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ func TestRolePermissions(t *testing.T) {
authorizer.APIKeyCreate,
authorizer.APIKeyUpdate,
authorizer.APIKeyDelete,
authorizer.ConnectorDelete,
authorizer.ConnectorUpdate,
authorizer.ConnectorSet,
},
},
{
Expand Down Expand Up @@ -149,6 +152,9 @@ func TestRolePermissions(t *testing.T) {
authorizer.APIKeyCreate,
authorizer.APIKeyUpdate,
authorizer.APIKeyDelete,
authorizer.ConnectorDelete,
authorizer.ConnectorUpdate,
authorizer.ConnectorSet,
},
},
{
Expand Down
12 changes: 12 additions & 0 deletions pkg/api/authorizer/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ const (
APIKeyCreate
APIKeyUpdate
APIKeyDelete

ConnectorDelete
ConnectorUpdate
ConnectorSet
)

var observerPermissions = []Permission{
Expand Down Expand Up @@ -122,6 +126,10 @@ var adminPermissions = []Permission{
APIKeyCreate,
APIKeyUpdate,
APIKeyDelete,

ConnectorDelete,
ConnectorUpdate,
ConnectorSet,
}

var ownerPermissions = []Permission{
Expand Down Expand Up @@ -176,4 +184,8 @@ var ownerPermissions = []Permission{
APIKeyCreate,
APIKeyUpdate,
APIKeyDelete,

ConnectorDelete,
ConnectorUpdate,
ConnectorSet,
}
38 changes: 38 additions & 0 deletions pkg/models/connector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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"`
// Status shows the connection status for the connector.
Status string `json:"status" bson:"-"`
// 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"`
}
33 changes: 33 additions & 0 deletions pkg/validator/validator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package validator

import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"reflect"
Expand Down Expand Up @@ -37,6 +39,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 @@ -121,6 +126,34 @@ var Rules = []Rule{
},
Error: fmt.Errorf("role must be \"owner\", \"administrator\", \"operator\" or \"observer\""),
},
{
Tag: PrivateKeyPEMTag,
Handler: func(field validator.FieldLevel) bool {
block, _ := pem.Decode([]byte(field.Field().String()))
if block == nil {
return false
}

key, err := x509.ParsePKCS8PrivateKey(block.Bytes)

return err == nil && key != nil
},
Error: fmt.Errorf("the private key is invalid"),
},
{
Tag: CertPEMTag,
Handler: func(field validator.FieldLevel) bool {
block, _ := pem.Decode([]byte(field.Field().String()))
if block == nil {
return false
}

cert, err := x509.ParseCertificate(block.Bytes)

return err == nil && cert != nil
},
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 54df9ee

Please sign in to comment.