Skip to content

Commit

Permalink
prepare SDK for the distributed KES implementation
Browse files Browse the repository at this point in the history
This commit changes the SDK w.r.t. to the upcoming
distributed KES server implementation.

In particular, the listing code has been changed
to a paginated implementation. However, backwards
compatibility with existing KES servers is maintained.

Signed-off-by: Andreas Auernhammer <[email protected]>
  • Loading branch information
aead committed Jun 26, 2023
1 parent a0da28d commit 17a1512
Show file tree
Hide file tree
Showing 15 changed files with 687 additions and 872 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.20.3
go-version: 1.20.5
check-latest: true
- name: Check out code
uses: actions/checkout@v3
Expand All @@ -40,7 +40,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.20.3
go-version: 1.20.5
check-latest: true
- name: Check out code
uses: actions/checkout@v3
Expand All @@ -56,7 +56,7 @@ jobs:
uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: 1.20.3
go-version: 1.20.5
check-latest: true
- name: Get govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
Expand Down
217 changes: 161 additions & 56 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,17 @@ type Client struct {
lb *loadBalancer
}

// NewClient returns a new KES client with the given
// KES server endpoint that uses the given TLS certificate
// mTLS authentication.
//
// The TLS certificate must be valid for client authentication.
//
// NewClient uses an http.Transport with reasonable defaults.
func NewClient(endpoint string, cert tls.Certificate) *Client {
// NewClient returns a new KES client that uses an API key
// for authentication.
func NewClient(endpoint string, key APIKey, options ...CertificateOption) (*Client, error) {
cert, err := GenerateCertificate(key, options...)
if err != nil {
return nil, err
}
return NewClientWithConfig(endpoint, &tls.Config{
MinVersion: tls.VersionTLS13,
MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{cert},
})
}), nil
}

// NewClientWithConfig returns a new KES client with the
Expand Down Expand Up @@ -265,31 +264,88 @@ func (c *Client) APIs(ctx context.Context) ([]API, error) {
return apis, nil
}

// CreateEnclave creates a new enclave with the given
// identity as enclave admin. Only the KES system
// admin can create new enclaves.
//
// It returns ErrEnclaveExists if the enclave already
// exists.
func (c *Client) CreateEnclave(ctx context.Context, name string, admin Identity) error {
// ExpandCluster expands a KES cluster by adding a new node with
// the given endpoint.
func (c *Client) ExpandCluster(ctx context.Context, endpoint string) error {
const (
APIPath = "/v1/enclave/create"
Method = http.MethodPost
APIPath = "/v1/cluster/expand"
Method = http.MethodPut
StatusOK = http.StatusOK
)
type Request struct {
Admin Identity `json:"admin"`
Endpoint string `json:"endpoint"`
}
c.init.Do(c.initLoadBalancer)

body, err := json.Marshal(Request{
Admin: admin,
Endpoint: endpoint,
})
if err != nil {
return err
}

client := retry(c.HTTPClient)
resp, err := c.lb.Send(ctx, &client, Method, c.Endpoints, join(APIPath, name), bytes.NewReader(body))
resp, err := c.lb.Send(ctx, &client, Method, c.Endpoints, APIPath, bytes.NewReader(body))
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != StatusOK {
return parseErrorResponse(resp)
}
return nil
}

// ShrinkCluster shrinks a KES cluster by removingt the new node with
// the given endpoint from the cluster.
func (c *Client) ShrinkCluster(ctx context.Context, endpoint string) error {
const (
APIPath = "/v1/cluster/shrink"
Method = http.MethodDelete
StatusOK = http.StatusOK
)
type Request struct {
Endpoint string `json:"endpoint"`
}
c.init.Do(c.initLoadBalancer)

body, err := json.Marshal(Request{
Endpoint: endpoint,
})
if err != nil {
return err
}

client := retry(c.HTTPClient)
resp, err := c.lb.Send(ctx, &client, Method, c.Endpoints, APIPath, bytes.NewReader(body))
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != StatusOK {
return parseErrorResponse(resp)
}
return nil
}

// CreateEnclave creates a new enclave with the given
// identity as enclave admin. Only the KES system
// admin can create new enclaves.
//
// It returns ErrEnclaveExists if the enclave already
// exists.
func (c *Client) CreateEnclave(ctx context.Context, name string) error {
const (
APIPath = "/v1/enclave/create"
Method = http.MethodPut
StatusOK = http.StatusOK
)
c.init.Do(c.initLoadBalancer)

client := retry(c.HTTPClient)
resp, err := c.lb.Send(ctx, &client, Method, c.Endpoints, join(APIPath, name), nil)
if err != nil {
return err
}
Expand Down Expand Up @@ -321,10 +377,6 @@ func (c *Client) DescribeEnclave(ctx context.Context, name string) (*EnclaveInfo
}
c.init.Do(c.initLoadBalancer)

if name == "" {
name = "default"
}

client := retry(c.HTTPClient)
resp, err := c.lb.Send(ctx, &client, Method, c.Endpoints, join(APIPath, name), nil)
if err != nil {
Expand All @@ -347,6 +399,48 @@ func (c *Client) DescribeEnclave(ctx context.Context, name string) (*EnclaveInfo
}, nil
}

// ListEnclaves returns a paginated list of enclave names from the server,
// starting at the specified prefix. If n > 0, it returns at most n names.
// Otherwise, the server determines the page size.
//
// ListEnclaves also returns a continuation token for fetching the next batch.
// When the listing reaches the end, the continuation token will be empty.
//
// The ListIter type can be used as a convenient way to iterate over a paginated list.
func (c *Client) ListEnclaves(ctx context.Context, prefix string, n int) ([]string, string, error) {
const (
APIPath = "/v1/enclave/list"
Method = http.MethodGet
StatusOK = http.StatusOK
MaxResponseSize = 1 * mem.MiB
)
type Response struct {
Names []string `json:"names"`
ContinueAt string `json:"continue_at"`
}
c.init.Do(c.initLoadBalancer)

client := retry(c.HTTPClient)
resp, err := c.lb.Send(ctx, &client, Method, c.Endpoints, join(APIPath, prefix), nil)
if err != nil {
return nil, "", err
}
defer resp.Body.Close()

if resp.StatusCode != StatusOK {
return nil, "", parseErrorResponse(resp)
}

var response Response
if err := json.NewDecoder(mem.LimitReader(resp.Body, MaxResponseSize)).Decode(&response); err != nil {
return nil, "", err
}
if n > 0 && n < len(response.Names) {
return response.Names[:n], response.Names[n], nil
}
return response.Names, response.ContinueAt, nil
}

// DeleteEnclave delete the specified enclave. Only the
// KES system admin can delete enclaves.
//
Expand Down Expand Up @@ -496,19 +590,21 @@ func (c *Client) DecryptAll(ctx context.Context, name string, ciphertexts ...CCP
return enclave.DecryptAll(ctx, name, ciphertexts...)
}

// ListKeys lists all names of cryptographic keys that match the given
// pattern. It returns a KeyIterator that iterates over all matched key
// names.
// ListKeys returns a paginated list of key names from the server,
// starting at the specified prefix. If n > 0, it returns at most n names.
// Otherwise, the server determines the page size.
//
// ListKeys also returns a continuation token for fetching the next batch.
// When the listing reaches the end, the continuation token will be empty.
//
// The pattern matching happens on the server side. If pattern is empty
// the KeyIterator iterates over all key names.
func (c *Client) ListKeys(ctx context.Context, pattern string) (*KeyIterator, error) {
// The ListIter type can be used as a convenient way to iterate over a paginated list.
func (c *Client) ListKeys(ctx context.Context, prefix string, n int) ([]string, string, error) {
enclave := Enclave{
Endpoints: c.Endpoints,
HTTPClient: c.HTTPClient,
lb: c.lb,
}
return enclave.ListKeys(ctx, pattern)
return enclave.ListKeys(ctx, prefix, n)
}

// CreateSecret creates a new secret with the given name.
Expand Down Expand Up @@ -560,31 +656,33 @@ func (c *Client) DeleteSecret(ctx context.Context, name string) error {
return enclave.DeleteSecret(ctx, name)
}

// ListSecrets returns a SecretIter that iterates over all secrets
// matching the pattern.
// ListSecrets returns a paginated list of secret names from the server,
// starting at the specified prefix. If n > 0, it returns at most n names.
// Otherwise, the server determines the page size.
//
// ListSecrets also returns a continuation token for fetching the next batch.
// When the listing reaches the end, the continuation token will be empty.
//
// The '*' pattern matches any secret. If pattern is empty the
// SecretIter iterates over all secrets names.
func (c *Client) ListSecrets(ctx context.Context, pattern string) (*SecretIter, error) {
// The ListIter type can be used as a convenient way to iterate over a paginated list.
func (c *Client) ListSecrets(ctx context.Context, prefix string, n int) ([]string, string, error) {
enclave := Enclave{
Endpoints: c.Endpoints,
HTTPClient: c.HTTPClient,
lb: c.lb,
}
return enclave.ListSecrets(ctx, pattern)
return enclave.ListSecrets(ctx, prefix, n)
}

// SetPolicy creates the given policy. If a policy with the same
// name already exists, SetPolicy overwrites the existing policy
// with the given one. Any existing identites will be assigned to
// the given policy.
func (c *Client) SetPolicy(ctx context.Context, name string, policy *Policy) error {
// CreatePolicy creates a new policy.
//
// It returns ErrPolicyExists if such a policy already exists.
func (c *Client) CreatePolicy(ctx context.Context, name string, policy *Policy) error {
enclave := Enclave{
Endpoints: c.Endpoints,
HTTPClient: c.HTTPClient,
lb: c.lb,
}
return enclave.SetPolicy(ctx, name, policy)
return enclave.CreatePolicy(ctx, name, policy)
}

// DescribePolicy returns the PolicyInfo for the given policy.
Expand Down Expand Up @@ -623,18 +721,21 @@ func (c *Client) DeletePolicy(ctx context.Context, name string) error {
return enclave.DeletePolicy(ctx, name)
}

// ListPolicies lists all policy names that match the given pattern.
// It returns a PolicyIterator that iterates over all matched policies.
// ListPolicies returns a paginated list of policy names from the server,
// starting at the specified prefix. If n > 0, it returns at most n names.
// Otherwise, the server determines the page size.
//
// ListPolicies also returns a continuation token for fetching the next batch.
// When the listing reaches the end, the continuation token will be empty.
//
// The pattern matching happens on the server side. If pattern is empty
// ListPolicies returns all policy names.
func (c *Client) ListPolicies(ctx context.Context, pattern string) (*PolicyIterator, error) {
// The ListIter type can be used as a convenient way to iterate over a paginated list.
func (c *Client) ListPolicies(ctx context.Context, prefix string, n int) ([]string, string, error) {
enclave := Enclave{
Endpoints: c.Endpoints,
HTTPClient: c.HTTPClient,
lb: c.lb,
}
return enclave.ListPolicies(ctx, pattern)
return enclave.ListPolicies(ctx, prefix, n)
}

// AssignPolicy assigns the policy to the identity.
Expand Down Expand Up @@ -690,17 +791,21 @@ func (c *Client) DeleteIdentity(ctx context.Context, identity Identity) error {
return enclave.DeleteIdentity(ctx, identity)
}

// ListIdentities lists all identites that match the given pattern.
// ListIdentities returns a paginated list of identities from the server,
// starting at the specified prefix. If n > 0, it returns at most n identities.
// Otherwise, the server determines the page size.
//
// ListIdentities also returns a continuation token for fetching the next batch.
// When the listing reaches the end, the continuation token will be empty.
//
// The pattern matching happens on the server side. If pattern is empty
// ListIdentities returns all identities.
func (c *Client) ListIdentities(ctx context.Context, pattern string) (*IdentityIterator, error) {
// The ListIter type can be used as a convenient way to iterate over a paginated list.
func (c *Client) ListIdentities(ctx context.Context, prefix string, n int) ([]Identity, string, error) {
enclave := Enclave{
Endpoints: c.Endpoints,
HTTPClient: c.HTTPClient,
lb: c.lb,
}
return enclave.ListIdentities(ctx, pattern)
return enclave.ListIdentities(ctx, prefix, n)
}

// AuditLog returns a stream of audit events produced by the
Expand Down
Loading

0 comments on commit 17a1512

Please sign in to comment.