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 +5/−0
- LICENSE +30/−0
- README.md +51/−0
- Setup.hs +2/−0
- main/smith.hs +47/−0
- smith-cli.cabal +56/−0
- src/Smith/Cli/Agent.hs +189/−0
- src/Smith/Cli/Command/Issue.hs +119/−0
- src/Smith/Cli/Configuration.hs +32/−0
- src/Smith/Cli/Data/Program.hs +12/−0
- src/Smith/Cli/Dispatch.hs +33/−0
- src/Smith/Cli/Error.hs +53/−0
- src/Smith/Cli/KeyPair.hs +65/−0
- src/Smith/Cli/Parser.hs +64/−0
+ 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."+ ]