package secretsserver import ( "encoding/base64" "io" "net/http" "git.handmadecities.com/HMC/shhh/pkg/secretsstore" ) type Server struct { store secretsstore.SecretsStore } // New creates a new secrets server that auths through mTLS. func New( store secretsstore.SecretsStore, ) *Server { return &Server{ store: store, } } // ListenAndServeTLS binds the server to addr. // cert must have been signed by ca, any client cert also // signed by ca will be able to call the serice. func (s *Server) ListenAndServeTLS(addr, ca, cert, key string) error { tlsConfig, err := mTLSConfigFromCA(ca) if err != nil { return err } server := &http.Server{ Addr: addr, Handler: s, TLSConfig: tlsConfig, // TODO: Tune the rest of the server parameters for robustness. } return server.ListenAndServeTLS(cert, key) } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // All operations are on the root path. if r.URL.Path != "" && r.URL.Path != "/" { w.WriteHeader(http.StatusBadRequest) // TODO: Better handling of `Write` failing. w.Write([]byte("requests are only accepted to the root (/) path")) return } // Decode key from query string parameters. b64Key := r.URL.Query().Get("key") if b64Key == "" { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("missing \"key\" query string parameter")) return } key, err := base64.RawURLEncoding.DecodeString(b64Key) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("key was not base64 encoded")) return } // TODO: Control flow is a little clumsy here, clean it up. switch r.Method { case http.MethodGet: secret, err := s.store.Get(secretsstore.Key(key)) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("could not retrieve requested secret: " + err.Error())) return } // We're writing raw bytes. w.Header().Add("Content-Type", "application/octet-stream") w.Write(secret) return case http.MethodPut: secret, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("could not read secret bytes from request body: " + err.Error())) return } if err := s.store.Put(secretsstore.Key(key), secret); err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("could not store secret: " + err.Error())) return } return case http.MethodDelete: if err := s.store.Delete(secretsstore.Key(key)); err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("could not delete secret: " + err.Error())) return } return default: w.WriteHeader(http.StatusBadRequest) w.Write([]byte(r.Method + " method is not allowd")) return } }