diff --git a/pkg/secretsclient/cli.go b/pkg/secretsclient/cli.go new file mode 100644 index 0000000..8273c5d --- /dev/null +++ b/pkg/secretsclient/cli.go @@ -0,0 +1,149 @@ +package secretsclient + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "io" + "net/http" +) + +type Client interface { + GetString(key string) (string, error) + PutString(key string, val string) error + + GetStringArr(key string) ([]string, error) + PutStringArr(key string, val []string) error + + GetStringMap(key string) (map[string]string, error) + PutStringMap(key string, val map[string]string) error + + Delete(key string) error +} + +func New(base, ca, cert, certKey string) (Client, error) { + h, err := newMTLSClient(ca, cert, certKey) + if err != nil { + return nil, err + } + return &client{ + base: base, + h: h, + }, nil +} + +type client struct { + base string + h *http.Client +} + +func (c *client) GetString(key string) (string, error) { + res, err := c.get(key) + if err != nil { + return "", err + } + return string(res), nil +} + +func (c *client) PutString(key, val string) error { + return c.put(key, []byte(val)) +} + +func (c *client) GetStringArr(key string) ([]string, error) { + arrBytes, err := c.get(key) + if err != nil { + return nil, err + } + + var res []string + err = json.Unmarshal(arrBytes, &res) + if err != nil { + return nil, err + } + return res, nil +} + +func (c *client) PutStringArr(key string, val []string) error { + arrBytes, err := json.Marshal(val) + if err != nil { + return err + } + return c.put(key, arrBytes) +} + +func (c *client) GetStringMap(key string) (map[string]string, error) { + mapBytes, err := c.get(key) + if err != nil { + return nil, err + } + + var res map[string]string + err = json.Unmarshal(mapBytes, &res) + if err != nil { + return nil, err + } + return res, nil +} + +func (c *client) PutStringMap(key string, val map[string]string) error { + mapBytes, err := json.Marshal(val) + if err != nil { + return err + } + return c.put(key, mapBytes) +} + +func (c *client) get(key string) ([]byte, error) { + res, err := c.h.Get(c.urlFromKey(key)) + if err != nil { + return nil, err + } + if res.StatusCode != http.StatusOK { + return nil, errors.New("request did not succeed") + } + val, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + defer res.Body.Close() + + return val, nil +} + +func (c *client) put(key string, val []byte) error { + body := bytes.NewBuffer(val) + req, err := http.NewRequest("PUT", c.urlFromKey(key), body) + if err != nil { + return err + } + res, err := c.h.Do(req) + if err != nil { + return err + } + if res.StatusCode != http.StatusOK { + return errors.New("request did not succeed") + } + return nil +} + +func (c *client) Delete(key string) error { + req, err := http.NewRequest("DELETE", c.urlFromKey(key), nil) + if err != nil { + return err + } + + res, err := c.h.Do(req) + if err != nil { + return err + } + if res.StatusCode != http.StatusOK { + return errors.New("request did not succeed") + } + return nil +} + +func (c *client) urlFromKey(key string) string { + b64Key := base64.RawURLEncoding.EncodeToString([]byte(key)) + return c.base + "?key=" + b64Key +} diff --git a/pkg/secretsclient/http.go b/pkg/secretsclient/http.go new file mode 100644 index 0000000..e2578ac --- /dev/null +++ b/pkg/secretsclient/http.go @@ -0,0 +1,38 @@ +package secretsclient + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net/http" + "os" + "time" +) + +func newMTLSClient(ca, cert, key string) (*http.Client, error) { + caPEMBytes, err := os.ReadFile(ca) + if err != nil { + return nil, err + } + caPool := x509.NewCertPool() + if !caPool.AppendCertsFromPEM(caPEMBytes) { + return nil, errors.New("could create a ca pool from file") + } + + cliCert, err := tls.LoadX509KeyPair(cert, key) + if err != nil { + return nil, fmt.Errorf("could not load client cert/key: %w", err) + } + + return &http.Client{ + // TODO: Tune other params. + Timeout: 10 * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: caPool, + Certificates: []tls.Certificate{cliCert}, + }, + }, + }, nil +}