packages feed

secretspec 0.16.0 → 0.17.0

raw patch · 3 files changed

+57/−8 lines, 3 files

Files

secretspec.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               secretspec-version:            0.16.0+version:            0.17.0 synopsis:           Haskell SDK for SecretSpec, a declarative secrets manager description:   A thin client over the @secretspec-ffi@ C ABI (linked at build time).
src/SecretSpec.hs view
@@ -23,6 +23,7 @@   , withPath   , withProvider   , withProfile+  , withScope   , withReason   , withNoValues     -- * Resolve (value-carrying)@@ -125,6 +126,8 @@ data Resolved = Resolved   { resolvedProvider        :: Text   , resolvedProfile         :: Text+  -- | Selected manifest scope, or 'Nothing' for a full-profile resolve (0.17+).+  , resolvedScope           :: Maybe Text   , resolvedSecrets         :: Map Text ResolvedSecret   , resolvedMissingOptional :: [Text]   } deriving (Show, Eq)@@ -157,6 +160,8 @@ data Report = Report   { reportProvider :: Text   , reportProfile  :: Text+  -- | Selected manifest scope, or 'Nothing' for a full-profile report (0.17+).+  , reportScope    :: Maybe Text   , reportSecrets  :: [SecretReport]   } deriving (Show, Eq) @@ -166,13 +171,14 @@   { bPath     :: Maybe Text   , bProvider :: Maybe Text   , bProfile  :: Maybe Text+  , bScope    :: Maybe Text   , bReason   :: Maybe Text   , bNoValues :: Bool   }  -- | A builder with no options set. builder :: Builder-builder = Builder Nothing Nothing Nothing Nothing False+builder = Builder Nothing Nothing Nothing Nothing Nothing False  -- | Resolve from a manifest at this path instead of walking up from the working -- directory.@@ -187,6 +193,10 @@ withProfile :: Text -> Builder -> Builder withProfile v b = b { bProfile = Just v } +-- | Limit resolution to a named manifest scope (SecretSpec 0.17+).+withScope :: Text -> Builder -> Builder+withScope v b = b { bScope = Just v }+ -- | Set a human-readable reason for this access (for audited providers). withReason :: Text -> Builder -> Builder withReason v b = b { bReason = Just v }@@ -249,15 +259,16 @@ load b = do   resp <- callNative (requestBytes b Nothing)   value <- responseValue resp resolveSchemaVersion "resolve"-  (prov, prof, secs, mreq, mopt) <- fromResult (parseEither pResolve value)+  (prov, prof, scope, secs, mreq, mopt) <- fromResult (parseEither pResolve value)   case mreq of-    [] -> pure (Resolved prov prof secs mopt)+    [] -> pure (Resolved prov prof scope secs mopt)     xs -> throwIO (MissingRequiredError xs)   where     pResolve = withObject "response" $ \o ->-      (,,,,)+      (,,,,,)         <$> o .: "provider"         <*> o .: "profile"+        <*> o .:? "scope"         <*> o .:? "secrets" .!= Map.empty         <*> o .:? "missing_required" .!= []         <*> o .:? "missing_optional" .!= []@@ -270,13 +281,14 @@ report b = do   resp <- callNative (requestBytes b (Just "report"))   value <- responseValue resp reportSchemaVersion "report"-  (prov, prof, secs) <- fromResult (parseEither pReport value)-  pure (Report prov prof secs)+  (prov, prof, scope, secs) <- fromResult (parseEither pReport value)+  pure (Report prov prof scope secs)   where     pReport = withObject "response" $ \o ->-      (,,)+      (,,,)         <$> o .: "provider"         <*> o .: "profile"+        <*> o .:? "scope"         <*> o .:? "secrets" .!= []  -- Build the request JSON for a resolve (@mode = Nothing@) or report@@ -288,6 +300,7 @@       [ ("path" .=) <$> bPath b       , ("provider" .=) <$> bProvider b       , ("profile" .=) <$> bProfile b+      , ("scope" .=) <$> bScope b       , ("reason" .=) <$> bReason b       ]       ++ ["no_values" .= True | bNoValues b]
test/Main.hs view
@@ -41,6 +41,7 @@   let tests =         [ ("abi_version_nonempty", testAbiVersion)         , ("missing_required_throws", testMissingRequired)+        , ("scoped_resolution", testScope)         , ("codegen", testCodegen)         ]           ++ concatMap conformanceTests fixtures@@ -92,6 +93,41 @@   case r of     Left _  -> pure ()     Right _ -> ioError (userError "expected MissingRequiredError")++testScope :: IO ()+testScope = do+  tmp <- getTemporaryDirectory+  let dir = tmp </> "secretspec-hs-scope"+  createDirectoryIfMissing True dir+  writeFile (dir </> "secretspec.toml") $+    unlines+      [ "[project]"+      , "name = \"hs-scope\""+      , "revision = \"1.0\""+      , ""+      , "[profiles.default]"+      , "DATABASE_URL = { description = \"DB\", required = true }"+      , "SENTRY_DSN = { description = \"Sentry\", required = false }"+      , ""+      , "[scopes.database]"+      , "secrets = [\"DATABASE_URL\"]"+      ]+  writeFile+    (dir </> ".env")+    "DATABASE_URL=postgres://db\nSENTRY_DSN=https://sentry\n"+  let scoped = fixtureBuilder dir & S.withScope "database"++  resolved <- S.load scoped+  expect (S.resolvedScope resolved == Just "database") "resolve scope was not returned"+  expect+    (Map.keys (S.resolvedSecrets resolved) == ["DATABASE_URL"])+    "resolve exposed an out-of-scope secret"++  rep <- S.report scoped+  expect (S.reportScope rep == Just "database") "report scope was not returned"+  expect+    (map S.srName (S.reportSecrets rep) == ["DATABASE_URL"])+    "report exposed an out-of-scope secret"  -- End-to-end codegen: secretspec schema -> quicktype --lang haskell -> compile -- the generated module and decode the SDK's own fieldsJson output with it, so