Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[knox] add no cache mode #103

Merged
merged 15 commits into from
Mar 1, 2024
200 changes: 200 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,206 @@ func (c *HTTPClient) getHTTPData(method string, path string, body url.Values, da
return nil
}


krockpot marked this conversation as resolved.
Show resolved Hide resolved
// UncachedHTTPClient is a client that uses HTTP to talk to Knox without caching.
type UncachedHTTPClient struct {
yoyouwen marked this conversation as resolved.
Show resolved Hide resolved
// Host is used as the host for http connections
Host string
//AuthHandler returns the authorization string for authenticating to knox. Users should be prefixed by 0u, machines by 0m. On fail, return empty string.
AuthHandler func() string
// Client is the http client for making network calls
Client HTTP
// Version is the current client version, useful for debugging and sent as a header
Version string
}

// NewClient creates a new uncached client to connect to talk to Knox.
func NewUncachedClient(host string, client HTTP, authHandler func() string, version string) APIClient {
return &HTTPClient{
Host: host,
Client: client,
AuthHandler: authHandler,
Version: version,
}
}

// NetworkGetKey gets a knox key by keyID and only uses network without the caches.
func (c *UncachedHTTPClient) NetworkGetKey(keyID string) (*Key, error) {
key := &Key{}
err := c.getHTTPData("GET", "/v0/keys/"+keyID+"/", nil, key)
if err != nil {
return nil, err
}

// do not return the invalid format remote keys
if key.ID == "" || key.ACL == nil || key.VersionList == nil || key.VersionHash == "" {
return nil, fmt.Errorf("invalid key content for the remote key")
}

return key, err
}

// CacheGetKey acts same as NetworkGetKey for UncachedHTTPClient.
func (c *UncachedHTTPClient) CacheGetKey(keyID string) (*Key, error) {
return c.NetworkGetKey(keyID)
}

// GetKey gets a knox key by keyID.
func (c *UncachedHTTPClient) GetKey(keyID string) (*Key, error) {
return c.NetworkGetKey(keyID)
}

// CacheGetKeyWithStatus acts same as NetworkGetKeyWithStatus for UncachedHTTPClient.
func (c *UncachedHTTPClient) CacheGetKeyWithStatus(keyID string, status VersionStatus) (*Key, error) {
return c.NetworkGetKeyWithStatus(keyID, status)
}

// NetworkGetKeyWithStatus gets a knox key by keyID and given version status (always calls network).
func (c *UncachedHTTPClient) NetworkGetKeyWithStatus(keyID string, status VersionStatus) (*Key, error) {
// If clients need to know
s, err := status.MarshalJSON()
if err != nil {
return nil, err
}

key := &Key{}
err = c.getHTTPData("GET", "/v0/keys/"+keyID+"/?status="+string(s), nil, key)
return key, err
}

// GetKeyWithStatus gets a knox key by keyID and status (no cache).
func (c *UncachedHTTPClient) GetKeyWithStatus(keyID string, status VersionStatus) (*Key, error) {
return c.NetworkGetKeyWithStatus(keyID, status)
}

// CreateKey creates a knox key with given keyID data and ACL.
func (c *UncachedHTTPClient) CreateKey(keyID string, data []byte, acl ACL) (uint64, error) {
var i uint64
d := url.Values{}
d.Set("id", keyID)
d.Set("data", base64.StdEncoding.EncodeToString(data))
s, err := json.Marshal(acl)
if err != nil {
return i, err
}
d.Set("acl", string(s))
err = c.getHTTPData("POST", "/v0/keys/", d, &i)
return i, err
}

// GetKeys gets all Knox (if empty map) or gets all keys in map that do not match key version hash.
func (c *UncachedHTTPClient) GetKeys(keys map[string]string) ([]string, error) {
var l []string

d := url.Values{}
for k, v := range keys {
d.Set(k, v)
}

err := c.getHTTPData("GET", "/v0/keys/?"+d.Encode(), nil, &l)
return l, err
}

// DeleteKey deletes a key from Knox.
func (c UncachedHTTPClient) DeleteKey(keyID string) error {
err := c.getHTTPData("DELETE", "/v0/keys/"+keyID+"/", nil, nil)
return err
}

// GetACL gets a knox key by keyID.
func (c *UncachedHTTPClient) GetACL(keyID string) (*ACL, error) {
acl := &ACL{}
err := c.getHTTPData("GET", "/v0/keys/"+keyID+"/access/", nil, acl)
return acl, err
}

// PutAccess will add an ACL rule to a specific key.
func (c *UncachedHTTPClient) PutAccess(keyID string, a ...Access) error {
d := url.Values{}
s, err := json.Marshal(a)
if err != nil {
return err
}
d.Set("acl", string(s))
err = c.getHTTPData("PUT", "/v0/keys/"+keyID+"/access/", d, nil)
return err
}

// AddVersion adds a key version to a specific key.
func (c *UncachedHTTPClient) AddVersion(keyID string, data []byte) (uint64, error) {
var i uint64
d := url.Values{}
d.Set("data", base64.StdEncoding.EncodeToString(data))
err := c.getHTTPData("POST", "/v0/keys/"+keyID+"/versions/", d, &i)
return i, err
}

// UpdateVersion either promotes or demotes a specific key version.
func (c *UncachedHTTPClient) UpdateVersion(keyID, versionID string, status VersionStatus) error {
d := url.Values{}
s, err := status.MarshalJSON()
if err != nil {
return err
}
d.Set("status", string(s))

err = c.getHTTPData("PUT", "/v0/keys/"+keyID+"/versions/"+versionID+"/", d, nil)
return err
}

func (c *UncachedHTTPClient) getClient() (HTTP, error) {
if c.Client == nil {
c.Client = &http.Client{}
}
return c.Client, nil
}

func (c *UncachedHTTPClient) getHTTPData(method string, path string, body url.Values, data interface{}) error {
r, err := http.NewRequest(method, "https://"+c.Host+path, bytes.NewBufferString(body.Encode()))

if err != nil {
return err
}

auth := c.AuthHandler()
if auth == "" {
return fmt.Errorf("No authentication data given. Use 'knox login' or set KNOX_USER_AUTH or KNOX_MACHINE_AUTH")
}
// Get user from env variable and machine hostname from elsewhere.
r.Header.Set("Authorization", auth)
r.Header.Set("User-Agent", fmt.Sprintf("Knox_Client/%s", c.Version))

if body != nil {
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}

cli, err := c.getClient()
if err != nil {
return err
}

resp := &Response{}
resp.Data = data
// Contains retry logic if we decode a 500 error.
for i := 1; i <= maxRetryAttempts; i++ {
err = getHTTPResp(cli, r, resp)
if err != nil {
return err
}
if resp.Status != "ok" {
if (resp.Code != InternalServerErrorCode) || (i == maxRetryAttempts) {
return fmt.Errorf(resp.Message)
}
time.Sleep(GetBackoffDuration(i))
} else {
break
}
}

return nil
}


func getHTTPResp(cli HTTP, r *http.Request, resp *Response) error {
w, err := cli.Do(r)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion client/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ var clientGetKeyMetrics = func(map[string]string) {}
func Run(
client knox.APIClient,
yoyouwen marked this conversation as resolved.
Show resolved Hide resolved
p *VisibilityParams,
loginCommand *Command) {
loginCommand *Command,
) {

cli = client
if p != nil {
Expand Down
4 changes: 4 additions & 0 deletions client/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ func parseTimeout(val string) (time.Duration, error) {
}

func runRegister(cmd *Command, args []string) *ErrorStatus {
if _, ok := cli.Type().FieldByName("KeyFolder"); !ok {
yoyouwen marked this conversation as resolved.
Show resolved Hide resolved
fmt.Println("Cannot Register in No Cache mode")
return nil
}
timeout, err := parseTimeout(*registerTimeout)
if err != nil {
return &ErrorStatus{fmt.Errorf("Invalid value for timeout flag: %s", err.Error()), false}
Expand Down