diff --git a/Dockerfile b/Dockerfile index 125ea46..6f28745 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,17 @@ FROM golang:alpine AS builder -WORKDIR /app -RUN apk add --no-cache musl-dev gcc ca-certificates +RUN apk add --no-cache musl-dev gcc COPY . . -RUN cd cmd/bws-cache && go build -ldflags='-s -w' -trimpath -o /dist/bws-cache + +RUN cd cmd/bws-cache && go build -ldflags='-s -w' -trimpath -o /dist/bws-cache RUN ldd /dist/bws-cache | tr -s [:blank:] '\n' | grep ^/ | xargs -I % install -D % /dist/% -FROM scratch + +FROM alpine COPY --from=builder /dist / -COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +RUN apk add --no-cache ca-certificates USER 65534 CMD ["/bws-cache", "start"] + diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 548cbda..8f63a24 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -36,6 +36,9 @@ func New(config *config.Config) http.Handler { router.Use(middleware.Recoverer) router.Use(middleware.Timeout(config.WebTTL)) + // Enable profiler + router.Mount("/debug", middleware.Profiler()) + slog.Debug("Router middleware setup finished") slog.Debug("Creating new bitwarden client connection") @@ -55,9 +58,10 @@ func New(config *config.Config) http.Handler { } func (api *API) getSecretByID(w http.ResponseWriter, r *http.Request) { - slog.Debug("Getting secret by ID") + ctx := r.Context() + slog.DebugContext(ctx, "Getting secret by ID") token, err := getAuthToken(r) - slog.Debug("Got auth token") + slog.DebugContext(ctx, "Got auth token") if err != nil { slog.Error(fmt.Sprintf("%+v", err)) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -65,53 +69,45 @@ func (api *API) getSecretByID(w http.ResponseWriter, r *http.Request) { } id := chi.URLParam(r, "secret_id") - slog.Debug("Connecting to bitwarden service") - api.Client.Connect(token) - defer api.Client.Close() - slog.Debug("Connected to bitwarden service") - - slog.Debug(fmt.Sprintf("Getting secret by ID: %s", id)) - res, err := api.Client.GetByID(id) + slog.DebugContext(ctx, fmt.Sprintf("Getting secret by ID: %s", id)) + res, err := api.Client.GetByID(ctx, id, token) if err != nil { - slog.Error(fmt.Sprintf("%+v", err)) + slog.ErrorContext(ctx, fmt.Sprintf("%+v", err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } - slog.Debug("Got secret") + slog.DebugContext(ctx, "Got secret") fmt.Fprint(w, res) } func (api *API) getSecretByKey(w http.ResponseWriter, r *http.Request) { - slog.Debug("Getting secret by key") + ctx := r.Context() + slog.DebugContext(ctx, "Getting secret by key") token, err := getAuthToken(r) if err != nil { - slog.Error(fmt.Sprintf("%+v", err)) + slog.ErrorContext(ctx, fmt.Sprintf("%+v", err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } key := chi.URLParam(r, "secret_key") - slog.Debug("Connecting to bitwarden service") - api.Client.Connect(token) - defer api.Client.Close() - slog.Debug("Connected to bitwarden service") - - slog.Debug(fmt.Sprintf("Searching for key: %s", key)) - res, err := api.Client.GetByKey(key, api.OrgID) + slog.DebugContext(ctx, fmt.Sprintf("Searching for key: %s", key)) + res, err := api.Client.GetByKey(ctx, key, api.OrgID, token) if err != nil { - slog.Error(fmt.Sprintf("%+v", err)) + slog.ErrorContext(ctx, fmt.Sprintf("%+v", err)) http.Error(w, err.Error(), http.StatusInternalServerError) return } - slog.Debug("Got key") + slog.DebugContext(ctx, "Got key") fmt.Fprint(w, res) } func (api *API) resetConnection(w http.ResponseWriter, r *http.Request) { - slog.Info("Resetting cache") + ctx := r.Context() + slog.InfoContext(ctx, "Resetting cache") api.Client.Cache.Reset() - slog.Info("Cache reset") + slog.InfoContext(ctx, "Cache reset") } func getAuthToken(r *http.Request) (string, error) { diff --git a/internal/pkg/client/client.go b/internal/pkg/client/client.go index 9a054fa..9478e87 100644 --- a/internal/pkg/client/client.go +++ b/internal/pkg/client/client.go @@ -1,6 +1,7 @@ package client import ( + "context" "encoding/json" "fmt" "log/slog" @@ -14,87 +15,65 @@ import ( ) type Bitwarden struct { - Client sdk.BitwardenClientInterface - Cache *cache.Cache - clientsInUse int - tokenPath string - mu sync.Mutex + Client sdk.BitwardenClientInterface + Cache *cache.Cache + tokenPath string + mu sync.Mutex } func New(ttl time.Duration) *Bitwarden { bw := Bitwarden{} slog.Debug("Setting up cache") bw.Cache = cache.New(ttl) + bw.tokenPath = fmt.Sprintf("/tmp/%s", uuid.New()) return &bw } -func (b *Bitwarden) Connect(token string) error { - b.mu.Lock() - var err error - if b.clientsInUse == 0 { - slog.Debug("Creating new bitwarden client connection") - b.Client, err = b.newClient(token) - if err != nil { - return err - } - } else { - slog.Debug("Client already open/created") - } - b.clientsInUse++ - b.mu.Unlock() - return nil +func (b *Bitwarden) connect(token string) error { + slog.Debug("Creating new bitwarden client connection") + return b.newClient(token) } -func (b *Bitwarden) newClient(token string) (sdk.BitwardenClientInterface, error) { - bitwardenClient, _ := sdk.NewBitwardenClient(nil, nil) - if b.tokenPath == "" { - b.tokenPath = fmt.Sprintf("/tmp/%s", uuid.New()) - } - err := bitwardenClient.AccessTokenLogin(token, &b.tokenPath) - if err != nil { - return nil, err - } - return bitwardenClient, nil +func (b *Bitwarden) newClient(token string) error { + b.Client, _ = sdk.NewBitwardenClient(nil, nil) + return b.Client.AccessTokenLogin(token, &b.tokenPath) } -func (b *Bitwarden) Close() { - b.mu.Lock() - b.clientsInUse-- - if b.clientsInUse == 0 { - slog.Debug("Closing bitwarden client connection") - b.Client.Close() - b.mu.Unlock() - return - } - slog.Debug("Client still in use not closing") - b.mu.Unlock() +func (b *Bitwarden) close() { + slog.Debug("Closing bitwarden client connection") + b.Client.Close() } -func (b *Bitwarden) GetByID(id string) (string, error) { - slog.Debug(fmt.Sprintf("Getting secret by ID: %s", id)) +func (b *Bitwarden) GetByID(ctx context.Context, id string, clientToken string) (string, error) { + slog.DebugContext(ctx, fmt.Sprintf("Getting secret by ID: %s", id)) value := b.Cache.GetSecret(id) if value != "" { slog.Debug(fmt.Sprintf("%s ID found in cache", id)) return value, nil } - secretIDs := make([]string, 1) - secretIDs[0] = id + slog.Debug(fmt.Sprintf("%s not found in cache, populating", id)) - secret, err := b.Client.Secrets().GetByIDS(secretIDs) + + secret, err := b.getSecretByIDs(ctx, id, clientToken) if secret == nil { return "", fmt.Errorf("unable to find secret: %s", id) } + if err != nil { + return "", err + } + secretJson, _ := json.Marshal(secret) b.Cache.SetSecret(id, string(secretJson)) - return string(secretJson), err + return string(secretJson), nil } -func (b *Bitwarden) GetByKey(key string, orgID string) (string, error) { +func (b *Bitwarden) GetByKey(ctx context.Context, key string, orgID string, clientToken string) (string, error) { secret := "" id := b.Cache.GetID(key) if id == "" { - slog.Debug(fmt.Sprintf("%s not found in cache, populating", key)) - keyList, err := b.Client.Secrets().List(orgID) + slog.DebugContext(ctx, fmt.Sprintf("%s not found in cache, populating", key)) + + keyList, err := b.getSecretList(ctx, orgID, clientToken) if err != nil { return "", err } @@ -108,12 +87,6 @@ func (b *Bitwarden) GetByKey(key string, orgID string) (string, error) { // query, but it returns all of them with a single query anyway if keyPair.Key == key { found = true - BwsSecret, err := b.Client.Secrets().Get(keyPair.ID) - if err != nil { - return "", err - } - storedSecret, _ := json.Marshal(BwsSecret) - b.Cache.SetSecret(keyPair.ID, string(storedSecret)) } } if !found { @@ -124,14 +97,68 @@ func (b *Bitwarden) GetByKey(key string, orgID string) (string, error) { } secret = b.Cache.GetSecret(id) if secret == "" { - slog.Debug(fmt.Sprintf("%s not found in cache, populating", key)) - BwsSecret, err := b.Client.Secrets().Get(id) + slog.DebugContext(ctx, fmt.Sprintf("%s not found in cache, populating", key)) + bwsSecret, err := b.getSecret(ctx, id, clientToken) if err != nil { return "", err } - storedSecret, _ := json.Marshal(BwsSecret) + storedSecret, _ := json.Marshal(bwsSecret) b.Cache.SetSecret(id, string(storedSecret)) secret = string(storedSecret) } return secret, nil } + +func (b *Bitwarden) getSecretList(ctx context.Context, orgID string, clientToken string) (*sdk.SecretIdentifiersResponse, error) { + slog.DebugContext(ctx, "getSecretList: Locking client") + b.mu.Lock() + + slog.DebugContext(ctx, "getSecretList: Opening client") + b.connect(clientToken) + + res, err := b.Client.Secrets().List(orgID) + slog.DebugContext(ctx, "getSecretList: Closing client") + b.close() + + slog.DebugContext(ctx, "getSecretList: Unlocking client") + b.mu.Unlock() + + return res, err +} + +func (b *Bitwarden) getSecret(ctx context.Context, id string, clientToken string) (*sdk.SecretResponse, error) { + slog.DebugContext(ctx, "getSecret: Locking client") + b.mu.Lock() + + slog.DebugContext(ctx, "getSecret: Opening client") + b.connect(clientToken) + + res, err := b.Client.Secrets().Get(id) + slog.DebugContext(ctx, "getSecret: Closing Client") + b.close() + + slog.DebugContext(ctx, "getSecret: Unlocking client") + b.mu.Unlock() + + return res, err +} + +func (b *Bitwarden) getSecretByIDs(ctx context.Context, id string, clientToken string) (*sdk.SecretsResponse, error) { + slog.DebugContext(ctx, "getSecretByIDs: Locking client") + b.mu.Lock() + + slog.DebugContext(ctx, "getSecretByIDs: Opening client") + b.connect(clientToken) + + secretIDs := make([]string, 1) + secretIDs[0] = id + res, err := b.Client.Secrets().GetByIDS(secretIDs) + + slog.DebugContext(ctx, "getSecretByIDs: Closing client") + b.close() + + slog.DebugContext(ctx, "getSecretByIDs: Unlocking client") + b.mu.Unlock() + + return res, err +}