packages feed

smith-cli (empty) → 0.0.1

raw patch · 14 files changed

+758/−0 lines, 14 filesdep +HsOpenSSLdep +attoparsecdep +basesetup-changed

Dependencies added: HsOpenSSL, attoparsec, base, base64-bytestring, bytestring, cereal, crypto-pubkey-openssh, crypto-pubkey-types, directory, filepath, network, openssh-protocol, optparse-applicative, smith-client, text, transformers, transformers-bifunctors, unix

Files

+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for smith-cli++## 0.1.0.0  -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2019, HotelKilo++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Mark Hibberd nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,51 @@+# smith-cli++This is a command line interface for the [Smith](https://smith.st).++The goal is to provide a convenient interface for working with smith.++### Configuration++The smith cli will source credentials configuration as follows:+ - It will check for an environment provided API key in '$SMITH_JWK'.+ - It will fall-back to looking for '$SMITH_HOME/credentials.json' if '$SMITH_HOME' is set.+ - It will fall-back to looking for '$HOME/.smith/credentials.json'.++The smith cli will source endpoint configuration as follows:+ - It will check for an environment provided endpoint in '$SMITH_ENDPOINT'.+ - It will fall-back to the public production endpoint 'https://api.smith.st'.+++### Stability++This cli is new, and should have the disclaimers that normally comes+with that. However, the command line aims to maintain compatibility+unless there is a non-small issue that breaking compatibility will+really address. Stable versions will always be available for+downoad/install if you really need to lock things down.+++### Example++Using your ssh-agent.+```+# using ssh agent+eval $(ssh-agent)++# issue a certificate for the muppets environment+smith --environment muppets+smith -e muppets+```++Using smith to start ssh-agent.+```+# start ssh-agent issue a certificate for the muppets environment+eval $(smith --environment muppets)+```++Running a command with access to an agent configured with your certificate.+```+# start ssh-agent issue a certificate for the muppets environment+smith --environment muppets -- ssh user@kermit+smith --environment muppets -- rsync -aH www www@gonzo:/var/www+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ main/smith.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE OverloadedStrings #-}++import           Control.Applicative ((<|>), some)++import qualified Options.Applicative as Options++import qualified Smith.Cli.Command.Issue as Issue+import           Smith.Cli.Data.Program (Program (..))+import qualified Smith.Cli.Dispatch as Dispatch+import qualified Smith.Cli.Error as Error+import qualified Smith.Cli.Parser as Parser++import qualified Smith.Client as Smith+import           Smith.Client.Data.CertificateRequest (Principal (..))+import           Smith.Client.Data.Environment (Environment (..))++import qualified System.Exit as Exit+++data Command =+    Command Environment [Principal] (Maybe Program)+    deriving (Eq, Ord, Show)+++-- |+-- The 'smith' executable, designed for smith users who+-- want to request access to servers.+--+main :: IO ()+main =+  Dispatch.dispatch parser >>= \a ->+    case a of+      Command environment principals program -> do+        smith <- Error.runOrFlailT Smith.renderSmithConfigureError $+          Smith.configureT+        Error.runOrFlailT Issue.renderIssueError $+          Issue.issue smith environment principals program+        Exit.exitSuccess+++-- FUTURE default to current user, not root?+parser :: Options.Parser Command+parser =+  Command+    <$> Parser.environment+    <*> ((some Parser.principal) <|> pure [Principal "root"])+    <*> Options.optional Parser.program
+ smith-cli.cabal view
@@ -0,0 +1,56 @@+name: smith-cli+version: 0.0.1+synopsis: Command line tool for <https://smith.st/ Smith>.+homepage: https://github.com/smith-security/smith-cli+license:  BSD3+license-file: LICENSE+author: Mark Hibberd+maintainer: mth@smith.st+copyright: (c) 2019, HotelKilo+bug-reports: https://github.com/smith-security/smith-cli/issues+category: Security+build-type: Simple+extra-source-files: ChangeLog.md, README.md+cabal-version: >= 1.10+description:+  This is a command line tool for interacting with <https://smith.st Smith>.+source-repository head+  type:     git+  location: git@github.com:smith-security/smith-cli.git++executable smith+  default-language: Haskell2010+  main-is: ../main/smith.hs+  hs-source-dirs: src+  build-depends:+      base >= 3 && < 5+    , attoparsec >= 0.9 && < 0.14+    , bytestring == 0.10.*+    , base64-bytestring == 1.0.*+    , cereal == 0.5.*+    , crypto-pubkey-openssh == 0.2.*+    , crypto-pubkey-types == 0.4.*+    , directory == 1.*+    , filepath == 1.*+    , HsOpenSSL == 0.11.*+    , network == 2.*+    , openssh-protocol == 0.0.*+    , optparse-applicative >= 0.11 && < 0.15+    , smith-client == 0.0.*+    , text == 1.*+    , transformers >= 0.4 && < 0.6+    , transformers-bifunctors == 0.*+    , unix+  other-modules:+    Smith.Cli.Agent+    Smith.Cli.Command.Issue+    Smith.Cli.Configuration+    Smith.Cli.Data.Program+    Smith.Cli.Dispatch+    Smith.Cli.Error+    Smith.Cli.KeyPair+    Smith.Cli.Parser+  ghc-options:+    -Wall+    -O2+    -threaded
+ src/Smith/Cli/Agent.hs view
@@ -0,0 +1,189 @@+-- |+-- Agent operations.+--+-- FUTURE: This should move to openssh-protocol.+--+{-# LANGUAGE OverloadedStrings #-}+module Smith.Cli.Agent (+    Agent+  , AddKeyError (..)+  , Message (..)+  , messageByte+  , Reply (..)+  , replyByte+  , connect+  , addKey+  , packet+  , send+  ) where++import           Control.Monad (void, unless)+import           Control.Monad.Trans.Bifunctor (BifunctorTrans (..))+import           Control.Monad.Trans.Except (ExceptT (..))+import           Control.Monad.IO.Class (MonadIO (..))++import qualified Crypto.OpenSSH.Protocol.Decode as Decode+import qualified Crypto.OpenSSH.Protocol.Encode as Encode++import qualified Data.Attoparsec.Text as Parser+import           Data.Bifunctor (Bifunctor (..))+import           Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString++import qualified Data.ByteString.Base64 as Base64+import qualified Data.Char as Char+import qualified Data.Serialize as Serialize+import           Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import           Data.Traversable (for)+import           Data.Word (Word8)++import           Network.Socket (Socket)+import qualified Network.Socket as Socket hiding (recv)+import qualified Network.Socket.ByteString as Socket++import qualified OpenSSL.RSA as OpenSSL++import           Smith.Client.Data.Certificate (Certificate (..))++import qualified System.Environment as Environment+import           System.IO (IO)+++data DeconstructedCertificate =+  DeconstructedCertificate {+      _certificateType :: Text+    , _certificateBlob :: ByteString+    , _certificateComment :: (Maybe Text)+    } deriving (Eq, Ord, Show)++data Agent =+  Agent Socket++connect :: IO (Maybe Agent)+connect = do+  path <- Environment.lookupEnv "SSH_AUTH_SOCK"+  for path $ \address -> do+    socket <- Socket.socket Socket.AF_UNIX Socket.Stream 0+    Socket.connect socket $ Socket.SockAddrUnix address+    pure $ Agent socket++data Message =+    AddIdentityMessage+    deriving (Eq, Ord, Show)++data Reply =+    FailureReply+  | SuccessReply+  | ExtensionFailureReply+  | IdentitiesAnswerReply+  | SignResponseReply+    deriving (Eq, Ord, Show)++messageByte :: Message -> Word8+messageByte m =+  case m of+    AddIdentityMessage ->+      17++replyByte :: Reply -> Word8+replyByte r =+  case r of+    FailureReply ->+      5+    SuccessReply ->+      6+    ExtensionFailureReply ->+      28+    IdentitiesAnswerReply ->+      12+    SignResponseReply ->+      14++packet :: Message -> Serialize.Put -> Serialize.Put+packet message payload =+  Encode.string . Serialize.runPut $ do+    Serialize.putWord8 $ messageByte message+    payload++data ProtocolError =+    InvalidResponseHeader ByteString+    deriving (Eq, Ord, Show)++send :: Agent -> Message -> Serialize.Put -> ExceptT ProtocolError IO ByteString+send (Agent agent) message payload = do+  liftIO . Socket.sendAll agent . Serialize.runPut $+    packet message payload+  header <- liftIO $ Socket.recv agent 4+  size <- ExceptT . pure . first (const $ InvalidResponseHeader header) $+    Serialize.runGet (Decode.uint32) header+  liftIO $ Socket.recv agent (fromIntegral size)++data AddKeyError =+    IncompleteKeyPair+  | InvalidCertificate+  | CouldNotAddKey+  | CouldNotAddCertificate+  | AddKeyProtocolError ProtocolError+    deriving (Eq, Ord, Show)++addKey :: Agent -> Certificate -> OpenSSL.RSAKeyPair -> ExceptT AddKeyError IO ()+addKey agent cert keys = do+  (DeconstructedCertificate t blob comment) <- maybe (left InvalidCertificate) pure $ deconstruct cert+  let+    n = OpenSSL.rsaN keys+    e = OpenSSL.rsaE keys+    d = OpenSSL.rsaD keys+    p = OpenSSL.rsaP keys+    q = OpenSSL.rsaQ keys+  iqmp <- maybe (left IncompleteKeyPair) pure $ OpenSSL.rsaIQMP keys++  rkey <- firstT AddKeyProtocolError $+    send agent AddIdentityMessage $ do+      Encode.string "ssh-rsa"+      Encode.mpint n+      Encode.mpint e+      Encode.mpint d+      Encode.mpint iqmp+      Encode.mpint p+      Encode.mpint q+      -- FUTURE Better default comment, server should send one.+      Encode.text $ maybe "" id comment++  unless (rkey == ByteString.singleton (replyByte SuccessReply)) $+    left CouldNotAddKey++  rcertificate <- firstT AddKeyProtocolError $+    send agent AddIdentityMessage $ do+      Encode.text t+      Encode.string blob+      Encode.mpint d+      Encode.mpint iqmp+      Encode.mpint p+      Encode.mpint q+      -- FUTURE Better default comment, server should send one.+      Encode.text $ maybe "" id comment++  unless (rcertificate == ByteString.singleton (replyByte SuccessReply)) $+    left CouldNotAddCertificate++deconstruct :: Certificate -> Maybe DeconstructedCertificate+deconstruct certificate =+  either (const Nothing) Just . flip Parser.parseOnly (getCertificate certificate) $ do+    t <- Parser.takeTill (Char.isSpace)+    void $ Parser.takeWhile (Char.isSpace)+    base64 <- Parser.takeTill (Char.isSpace)+    blob <- case Base64.decode . Text.encodeUtf8 $ base64 of+      Left e ->+        fail e+      Right v ->+        pure v+    void $ Parser.takeWhile (Char.isSpace)+    comment <- Parser.takeTill (Char.isSpace)+    pure $ DeconstructedCertificate t blob+      (if Text.null comment then Nothing else Just comment)++left :: Monad m => x -> ExceptT x m a+left =+  ExceptT . pure . Left
+ src/Smith/Cli/Command/Issue.hs view
@@ -0,0 +1,119 @@+-- |+-- Command for requesting a certificate.+--+{-# LANGUAGE OverloadedStrings #-}+module Smith.Cli.Command.Issue (+    -- * Entry point+    issue++    -- * Errors+  , IssueError (..)+  , renderIssueError+  ) where++import           Control.Monad.IO.Class (MonadIO (..))+import           Control.Monad.Trans.Bifunctor (BifunctorTrans (..))+import           Control.Monad.Trans.Except (ExceptT (..))++import           Data.Foldable (for_)+import           Data.Text (Text)+import qualified Data.Text as Text++import qualified Smith.Cli.Agent as Agent+import           Smith.Cli.Data.Program (Program (..))+import           Smith.Cli.KeyPair (Comment (..), EncodedRSAKeyPair (..))+import qualified Smith.Cli.KeyPair as KeyPair++import           Smith.Client (Smith (..))+import qualified Smith.Client as Smith+import           Smith.Client.Error (SmithError (..), ErrorCode (..))+import           Smith.Client.Data.CertificateRequest (Principal (..), PublicKey (..), CertificateRequest (..))+import           Smith.Client.Data.Environment (Environment (..))++import qualified System.Posix.Process as Posix++-- |+-- Requests a certificate, registering it with ssh-agent if possible.+--+-- If an ssh-agent is not currently running, it is started with+-- output to 'System.IO.stdout' suitable for use with eval.+--+-- If a program is provided, the program is executed with an+-- ssh-agent configured.+--+issue :: Smith -> Environment -> [Principal] -> Maybe Program -> ExceptT IssueError IO ()+issue smith environment principals program = do+  agent <- liftIO Agent.connect >>= maybe (left AgentNotAvailableError) pure+  keys <- liftIO KeyPair.newRSAKeyPair+  encoded <- maybe (left KeyGenerationError) pure $+    KeyPair.encodeRSAKeyPair (Comment . mconcat $ ["smith-", getEnvironment environment]) keys+  certificate <- firstT SmithIssueError .+    Smith.runRequestT smith . Smith.issue $+      CertificateRequest+        (PublicKey $ sshRSAPublicKey encoded)+        principals+        environment+        Nothing+  firstT IssueAddKeyError $+    Agent.addKey agent certificate keys+  liftIO $ for_ program exec++exec :: Program -> IO a+exec (Program command arguments) =+  Posix.executeFile+    (Text.unpack command)+    True+    (Text.unpack <$> arguments)+    Nothing++data IssueError =+    SmithIssueError SmithError+  | AgentNotAvailableError+  | KeyGenerationError+  | IssueAddKeyError Agent.AddKeyError+    deriving (Eq, Show)++renderIssueError :: IssueError -> Text+renderIssueError e =+  case e of+    SmithIssueError err ->+      renderSmithError err+    AgentNotAvailableError ->+      "ssh-agent was not available, ensure it is running and SSH_AUTH_SOCK is set."+    KeyGenerationError ->+      "We couldn't generate a key-pair using OpenSSL for this access request, this is very unusual, try again and please raise a support issue if this persists."+    IssueAddKeyError Agent.IncompleteKeyPair ->+      "An invalid key-pair has been generated (via OpenSSL), this very unusual, try again and please raise a support issue if this persists."+    IssueAddKeyError Agent.InvalidCertificate ->+      "Smith has generated an invalid certificate, please ensure you are running a compatible client version. If running the correct client version please raise a support issue."+    IssueAddKeyError Agent.CouldNotAddKey ->+      "Smith has generated a key but could not add it to your ssh-agent. Please raise a support issue, and include your ssh-agent implementation and version."+    IssueAddKeyError Agent.CouldNotAddCertificate ->+      "Smith has generated a certificate but could not add it to your ssh-agent. Please raise a support issue, and include your ssh-agent implementation and version. Note that if you are using the gnome ssh-agent instead of openssh, it does not support certificates."+    IssueAddKeyError (Agent.AddKeyProtocolError _) ->+      "Smith had a problem talking to your ssh-agent and could not add key or certificate. Please raise a support issue, and include your ssh-agent implementation and version."++renderSmithError :: SmithError -> Text+renderSmithError e =+  case e of+    -- FUTURE: debug mode that prints message.+    -- FUTURE: Handle specific error codes for better error messages.+    SmithApplicationError code _message ->+      mconcat ["There was an error performing your request [", getErrorCode code, "]."]+    -- FUTURE: debug mode that prints message.+    SmithAuthorizationError code _message ->+      mconcat ["You are not authorized to perform this request [", getErrorCode code, "]."]+    SmithAuthenticationError _err ->+      mconcat ["Smith could not authenticate you, please check your credentials and connectivity to Smith."]+    -- FUTURE: debug mode that prints body + message+    SmithResponseParseError code _body _message ->+      mconcat ["Smith response parse error [", Text.pack . show $ code, "]. Please check connectivity to Smith and retry request."]+    -- FUTURE: debug mode that prints body.+    SmithStatusCodeError code _body ->+      mconcat ["Smith status code error [", Text.pack . show $ code, "]. Please check connectivity to Smith and retry request."]+    SmithUrlParseError message ->+      mconcat ["Smith client url-parse error [", message, "]. Check you are running the latest client version, and raise a supportissue if this issue persists ."]++left :: Applicative m => x -> ExceptT x m a+left =+  ExceptT . pure . Left
+ src/Smith/Cli/Configuration.hs view
@@ -0,0 +1,32 @@+-- |+-- Command line configuration.+--+{-# LANGUAGE OverloadedStrings #-}+module Smith.Cli.Configuration (+    Configuration (..)+  , configure+  ) where++import qualified System.Directory as Directory+import qualified System.Environment as Environment+import           System.FilePath (FilePath, (</>))+++data Configuration =+  Configuration {+      configurationSmithHome :: FilePath+    } deriving (Eq, Ord, Show)+++configure :: IO Configuration+configure = do+  Environment.lookupEnv "SMITH_HOME" >>=+    maybe+      (Configuration <$> defaultSmithHome)+      (pure . Configuration)+++defaultSmithHome :: IO FilePath+defaultSmithHome =+  Directory.getHomeDirectory >>= \home ->+    pure (home </> ".smith")
+ src/Smith/Cli/Data/Program.hs view
@@ -0,0 +1,12 @@+-- |+-- Data types respresenting a program to execute.+--+module Smith.Cli.Data.Program (+    Program (..)+  ) where++import           Data.Text (Text)++data Program =+    Program Text [Text]+    deriving (Eq, Ord, Show)
+ src/Smith/Cli/Dispatch.hs view
@@ -0,0 +1,33 @@+-- |+-- 'Options.Applicative' based dispatch code.+--+-- /danger:/ This module is designed to call System.Exit+-- and as such should only be called from the main module.+--+module Smith.Cli.Dispatch (+    dispatch+  ) where++import           Options.Applicative ((<**>))+import qualified Options.Applicative as Options+++-- |+-- Runs the provided command line parser with+-- sensible defaults.+--+-- On parser failure, prints error message, usage and+-- exits with a status code of @1@.+--+-- On parser success, the value will be returned.+--+dispatch :: Options.Parser a -> IO a+dispatch parser = do+  Options.customExecParser+    (Options.prefs . mconcat $ [+        Options.showHelpOnEmpty+      , Options.showHelpOnError+      ])+    (Options.info+      (parser <**> Options.helper)+      Options.idm)
+ src/Smith/Cli/Error.hs view
@@ -0,0 +1,53 @@+-- |+-- Command line error handling routines.+--+-- /danger:/ This module is designed to call System.Exit+-- and as such should only be called from the main module.+--+module Smith.Cli.Error (+    flail+  , runOrFlailT+  , runOrFlail+  ) where++import           Control.Monad.Trans.Except (ExceptT (..), runExceptT)++import           Data.Text (Text)+import qualified Data.Text.IO as Text++import qualified System.Exit as Exit+import qualified System.IO as IO+++-- |+-- On left case, the handler will be used to print error+-- message to 'System.IO.stderr' and exit with a status+-- code of @1@.+--+-- On right case, the value will be returned.+--+runOrFlailT :: (e -> Text) -> ExceptT e IO a -> IO a+runOrFlailT handler =+  runOrFlail handler . runExceptT+++-- |+-- On left case, the handler will be used to print error+-- message to 'System.IO.stderr' and exit with a status+-- code of @1@.+--+-- On right case, the value will be returned.+--+runOrFlail :: (e -> Text) -> IO (Either e a) -> IO a+runOrFlail handler action =+  action >>= either (flail . handler) pure+++-- |+-- Print error message to 'System.IO.stderr' and exit with+-- a status code of @1@.+--+flail :: Text -> IO a+flail msg = do+  Text.hPutStrLn IO.stderr msg+  Exit.exitFailure
+ src/Smith/Cli/KeyPair.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE OverloadedStrings #-}+module Smith.Cli.KeyPair (+    EncodedRSAKeyPair (..)+  , Comment (..)+  , newRSAKeyPair+  , encodeRSAKeyPair+  ) where++import qualified Crypto.PubKey.OpenSsh as OpenSsh+import qualified Crypto.Types.PubKey.RSA as RSA++import           Data.Text (Text)+import qualified Data.Text.Encoding as Text++import qualified OpenSSL.RSA as OpenSSL++import           System.IO (IO)+++newtype Comment =+  Comment {+      getComment :: Text+    } deriving (Eq, Ord, Show)++data EncodedRSAKeyPair =+  EncodedRSAKeyPair {+      sshRSAPublicKey :: Text+    , sshRSAPrivateKey :: Text+    } deriving (Eq, Ord, Show)++-- RSA exponent matches ssh-keygen, see OpenSSL constant RSA_F4.+-- https://github.com/openssh/openssh-portable/blob/7d68e262944c1fff1574600fe0e5e92ec8b398f5/sshkey.c#L1518+newRSAKeyPair :: IO OpenSSL.RSAKeyPair+newRSAKeyPair =+  OpenSSL.generateRSAKey'+    4096+    (0x10001 {- OpenSSL.RSA_F4 = 65537 = 0x10001 -})++encodeRSAKeyPair :: Comment -> OpenSSL.RSAKeyPair -> Maybe EncodedRSAKeyPair+encodeRSAKeyPair comment openssl =+  let+    public =+      RSA.PublicKey {+          RSA.public_size = OpenSSL.rsaSize openssl+        , RSA.public_n = OpenSSL.rsaN openssl+        , RSA.public_e = OpenSSL.rsaE openssl+        }+    mprivate = do+      dP <- OpenSSL.rsaDMP1 openssl+      dQ <- OpenSSL.rsaDMQ1 openssl+      qinv <- OpenSSL.rsaIQMP openssl+      pure $ RSA.PrivateKey {+          RSA.private_pub = public+        , RSA.private_d = OpenSSL.rsaD openssl+        , RSA.private_p = OpenSSL.rsaP openssl+        , RSA.private_q = OpenSSL.rsaQ openssl+        , RSA.private_dP = dP+        , RSA.private_dQ = dQ+        , RSA.private_qinv = qinv+        }+  in+    flip fmap mprivate $ \private ->+      EncodedRSAKeyPair+        (Text.decodeUtf8 . OpenSsh.encodePublic . OpenSsh.OpenSshPublicKeyRsa public . Text.encodeUtf8 . getComment $ comment)+        (Text.decodeUtf8 . OpenSsh.encodePrivate $ OpenSsh.OpenSshPrivateKeyRsa private)
+ src/Smith/Cli/Parser.hs view
@@ -0,0 +1,64 @@+-- |+-- 'Options.Applicative' based parsers for use within+-- the command line tools. This are centralised to+-- help with consistency across various commands.+--+-- This module is designed to be import qualified.+--+-- > import qualified Smith.Cli.Parser as Parser+--+module Smith.Cli.Parser (+  -- * flags+    environment+  , principal++  -- * arguments+  , program+  ) where++import           Control.Applicative (many)++import           Data.Text (Text)+import qualified Data.Text as Text++import qualified Options.Applicative as Options++import           Smith.Cli.Data.Program (Program (..))+import           Smith.Client.Data.Environment (Environment (..))+import           Smith.Client.Data.CertificateRequest (Principal (..))++environment :: Options.Parser Environment+environment =+ fmap Environment . fmap Text.pack . Options.strOption . mconcat $ [+      Options.long "environment"+    , Options.short 'e'+    , Options.metavar "ENVIRONMENT"+    ]++-- CONSIDER: 'user' alias or similar.+principal :: Options.Parser Principal+principal =+ fmap Principal . fmap Text.pack . Options.strOption . mconcat $ [+      Options.long "principal"+    , Options.short 'p'+    , Options.metavar "PRINCIPAL"+    ]++program :: Options.Parser Program+program =+  Program+    <$> programName+    <*> many programArgument++programName :: Options.Parser Text+programName =+  fmap Text.pack . Options.strArgument . mconcat $ [+      Options.metavar "PROGRAM"+    , Options.help "Program to execute."+    ]+programArgument :: Options.Parser Text+programArgument =+ fmap Text.pack . Options.strArgument . mconcat $ [+      Options.metavar "ARGUMENT"+    , Options.help "Argument to provided program."+    ]