Skip to content

Commit a4e8881

Browse files
committed
[cli] feat: allow for ANTI_SSH_KEY_SELECTOR to be missing
1 parent 1fbc66c commit a4e8881

File tree

7 files changed

+111
-36
lines changed

7 files changed

+111
-36
lines changed

cli/docs/requester-role.md

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,28 +104,44 @@ Then commit and push the changes to your repository.
104104
105105
### Requesting a test-run
106106
107+
#### SSH key setup
107108
Before proceding be careful to set the necessary signing assets in your environment variables.
108-
```bash
109-
Which key selector to use from the SSH file
110-
env: ANTI_SSH_KEY_SELECTOR STRING
111109
112-
Path to the SSH private key file
113-
env: ANTI_SSH_FILE FILEPATH
110+
- `anti` will use the SSH private key to sign the request
111+
- The private key has to be an ed25519 key.
112+
- The public key corresponding to the private key has to be registered in your github account [see above](#registering-a-user-public-key).
113+
114+
Multiple keys file are supported, in this case you have to specify which key to use with the `ANTI_SSH_KEY_SELECTOR` environment variable or the `--ssh-key-selector` option.
115+
In case you don't know the selector, you can inspect the keys in your file with
116+
117+
```bash
118+
anti ssh-selectors --ssh-file PATH_TO_YOUR_SSH_FILE --ask-ssh-passphrase
114119
```
115120
116-
As with the wallet passphrase you can set the password in the environment variable
121+
If multiple keys are present in the file and you don't specify a selector, the first key in the file will be used.
122+
123+
To link to your private key file, set the `ANTI_SSH_FILE` environment variable to point to it.
124+
125+
126+
As with the wallet passphrase you can set the password in the environment variable (not recommended)
117127
118128
```bash
119-
read -s -p "Enter password to decrypt the SSH private key: " ANTI_INTERACTIVE_SECRETS
120-
export ANTI_INTERACTIVE_SECRETS
129+
read -s -p "Enter password to decrypt the SSH private key: " ANTI_SSH_PASSWORD
130+
export ANTI_SSH_PASSWORD
121131
```
122132
123-
Or better paste it from a password manager each time you need it using the 'ask-password' option
133+
Or better paste it from a password manager each time you need it using the `--ask-ssh-password` option
124134
125-
Or set the `ANTI_INTERACTIVE_SECRETS` environment variable to any value.
135+
Or set the `ANTI_INTERACTIVE_SECRETS` environment variable to any value to imply the `--ask-ssh-password` option
136+
137+
```bash
138+
export ANTI_INTERACTIVE_SECRETS=1
139+
```
126140
127141
> The file at ANTI_SSH_FILE path has to be the encrypted ssh private key matching the user registration [see above](#registering-a-user-public-key).
128142
143+
#### Requesting the test-run
144+
129145
To request a test-run, you can use the `anti requester create-test` command.
130146
131147
```bash
@@ -135,7 +151,7 @@ anti requester create-test --platform github --username alice --repository youro
135151
136152
You can request multiple test-runs for the same commit but you have to specify a different `--try` number for each request.
137153
138-
### Checking the test-run status
154+
#### Checking the test-run status
139155
140156
You can check the status of your test-run requests with the `anti facts test-runs` command.
141157
@@ -146,5 +162,5 @@ anti facts test-runs -i <your_test_run_id>
146162
You can find all running test-runs for a user with
147163
148164
```bash
149-
anti facts test-runs running --whose cfhal
165+
anti facts test-runs running --whose alice
150166
```

cli/justfile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ E2E match="":
6767
export ANTI_TEST_REQUESTER_WALLET=tmp/test.json
6868
export ANTI_TEST_ORACLE_WALLET=tmp/test.json
6969
export ANTI_TEST_AGENT_WALLET=tmp/test.json
70-
7170
export ANTI_SSH_FILE=test-E2E/fixtures/cfhal_ed25519
72-
export ANTI_SSH_KEY_SELECTOR=cfhal
7371
export ANTI_WAIT=2
7472
while [[ "$(curl -s "localhost:$MPFS_PORT/tokens" | jq -r '.indexerStatus.ready')" != "true" ]]; do
7573
echo "Waiting for indexer to be ready..."

cli/src/Cli.hs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ import Data.Functor.Identity (Identity (..))
1515
import Facts (FactsSelection, factsCmd)
1616
import GitHub (Auth)
1717
import Lib.JSON.Canonical.Extra
18-
import Lib.SSH.Private (SSHClient, WithSelector (..), sshKeySelectors)
18+
import Lib.SSH.Private
19+
( SSHClient
20+
, Selection
21+
, WithSelector (..)
22+
, sshKeySelectors
23+
)
1924
import MPFS.API
2025
( MPFS (..)
2126
, mpfsClient
@@ -70,7 +75,7 @@ data Command a where
7075
-> TokenId
7176
-> Command
7277
(AValidationResult TokenInfoFailure (Token WithValidation))
73-
SSHSelectors :: SSHClient 'WithoutSelector -> Command [String]
78+
SSHSelectors :: SSHClient 'WithoutSelector -> Command [Selection]
7479

7580
data SetupError = TokenNotSpecified
7681
deriving (Show, Eq)

cli/src/Lib/SSH/Private.hs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module Lib.SSH.Private
1515
, mkKeyAPI
1616
, WithSelector (..)
1717
, SelectorField
18+
, Selection (..)
1819
) where
1920

2021
import Control.Applicative (many, (<|>))
@@ -51,11 +52,14 @@ import Data.ByteString.Char8 qualified as BC
5152
import Data.ByteString.Lazy qualified as BL
5253
import Data.Map.Strict qualified as Map
5354
import Data.Word (Word8)
55+
import Lib.JSON.Canonical.Extra (object, (.=))
56+
import Lib.SSH.Public (SSHPublicKey (..), encodeSSHPublicKey)
57+
import Text.JSON.Canonical (ToJSON (..))
5458

5559
data WithSelector = WithSelector | WithoutSelector
5660

5761
type family SelectorField (w :: WithSelector) where
58-
SelectorField 'WithSelector = String
62+
SelectorField 'WithSelector = Maybe String
5963
SelectorField 'WithoutSelector = ()
6064

6165
data SSHClient sel = SSHClient
@@ -74,7 +78,7 @@ type Sign = BC.ByteString -> Ed25519.Signature
7478
sign :: KeyPair -> Sign
7579
sign (KeyPair pk sk) = Ed25519.sign sk pk
7680

77-
mkKeyAPI :: String -> ByteString -> String -> Maybe KeyPair
81+
mkKeyAPI :: String -> ByteString -> Maybe String -> Maybe KeyPair
7882
mkKeyAPI passPhrase content sshKeySelector =
7983
let
8084
ks = decodePrivateKeyFile (BC.pack passPhrase) content
@@ -85,7 +89,8 @@ mkKeyAPI passPhrase content sshKeySelector =
8589
, privateKey = sk
8690
}
8791
in
88-
Map.lookup sshKeySelector $ foldMap mkMap ks
92+
maybe (fmap snd <$> Map.lookupMin) Map.lookup sshKeySelector
93+
$ foldMap mkMap ks
8994

9095
sshKeyPair
9196
:: SSHClient 'WithSelector
@@ -94,11 +99,27 @@ sshKeyPair SSHClient{sshKeySelector, sshKeyFile, sshKeyPassphrase} = do
9499
content <- B.readFile sshKeyFile
95100
pure $ mkKeyAPI sshKeyPassphrase content sshKeySelector
96101

97-
sshKeySelectors :: SSHClient 'WithoutSelector -> IO [String]
102+
data Selection = Selection
103+
{ selectorName :: String
104+
, selectorKey :: String
105+
}
106+
deriving (Show, Eq)
107+
108+
instance Monad m => ToJSON m Selection where
109+
toJSON (Selection name key) =
110+
object
111+
[ "name" .= name
112+
, "publicKey" .= key
113+
]
114+
115+
sshKeySelectors :: SSHClient 'WithoutSelector -> IO [Selection]
98116
sshKeySelectors SSHClient{sshKeyFile, sshKeyPassphrase} = do
99117
content <- B.readFile sshKeyFile
100118
let ks = decodePrivateKeyFile (BC.pack sshKeyPassphrase) content
101-
pure $ fmap (BC.unpack . snd) ks
119+
let f (KeyPair k _, comment) = Selection (BC.unpack comment) render
120+
where
121+
SSHPublicKey render = encodeSSHPublicKey k
122+
pure $ fmap f ks
102123

103124
data KeyPair
104125
= KeyPair

cli/src/User/Requester/Options.hs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import OptEnvConf
3434
, long
3535
, metavar
3636
, option
37+
, optional
3738
, reader
3839
, setting
3940
, str
@@ -129,7 +130,7 @@ sshClientOption
129130
:: Parser (SSHClient 'WithSelector)
130131
sshClientOption =
131132
SSHClient
132-
<$> keySelectorOption
133+
<$> optional keySelectorOption
133134
<*> keyFileOption
134135
<*> keyPasswordOption
135136

@@ -144,7 +145,8 @@ keySelectorOption :: Parser String
144145
keySelectorOption =
145146
setting
146147
[ env "ANTI_SSH_KEY_SELECTOR"
147-
, help "Which key selector to use from the SSH file"
148+
, help
149+
"Which key selector to use from the SSH file, it will use the first one if not specified"
148150
, metavar "STRING"
149151
, reader str
150152
, long "ssh-key-selector"

cli/test/Lib/SSH/KeySpec.hs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,64 @@ import Data.ByteString qualified as B
88
import Lib.SSH.Private
99
( KeyPair (..)
1010
, SSHClient (..)
11+
, Selection (..)
1112
, WithSelector (..)
1213
, sign
1314
, sshKeyPair
15+
, sshKeySelectors
1416
)
15-
import Test.Hspec (Spec, beforeAll, describe, it)
16-
import Test.QuickCheck (Testable (property))
17+
import Test.Hspec (Spec, beforeAll, describe, it, shouldBe)
18+
import Test.QuickCheck
19+
( Testable (property)
20+
, ioProperty
21+
, withMaxSuccess
22+
)
23+
24+
clientWithSelector :: Maybe String -> SSHClient 'WithSelector
25+
clientWithSelector sel =
26+
SSHClient
27+
{ sshKeySelector = sel
28+
, sshKeyFile = "test-E2E/fixtures/test_ed25519"
29+
, sshKeyPassphrase = "pw"
30+
}
1731

18-
client :: SSHClient 'WithSelector
19-
client =
32+
clientWithoutSelector :: SSHClient 'WithoutSelector
33+
clientWithoutSelector =
2034
SSHClient
21-
{ sshKeySelector = "test_user"
35+
{ sshKeySelector = ()
2236
, sshKeyFile = "test-E2E/fixtures/test_ed25519"
2337
, sshKeyPassphrase = "pw"
2438
}
2539

26-
readKey :: IO KeyPair
27-
readKey = do
28-
Just api <- sshKeyPair client
40+
readKey :: Maybe String -> IO KeyPair
41+
readKey user = do
42+
Just api <- sshKeyPair $ clientWithSelector user
2943
pure api
3044

45+
readSelectors :: IO [Selection]
46+
readSelectors = sshKeySelectors clientWithoutSelector
47+
3148
spec :: Spec
3249
spec = do
33-
describe "SSH Key" $ beforeAll readKey $ do
34-
it "should sign and verify a message" $ \k@KeyPair{publicKey} -> do
35-
property $ \msgb -> do
50+
describe "SSH Key with selector" $ do
51+
it "should sign and verify a message" $ ioProperty $ do
52+
k@KeyPair{publicKey} <- readKey $ Just "test_user"
53+
pure $ property $ withMaxSuccess 10 $ \msgb -> do
54+
let msg = B.pack msgb
55+
let signed = sign k msg
56+
Ed25519.verify publicKey msg signed
57+
it "should sign and verify a message with the first key" $ ioProperty $ do
58+
k@KeyPair{publicKey} <- readKey Nothing
59+
pure $ property $ withMaxSuccess 10 $ \msgb -> do
3660
let msg = B.pack msgb
3761
let signed = sign k msg
3862
Ed25519.verify publicKey msg signed
63+
describe "SSH Key without selector" $ beforeAll readSelectors $ do
64+
it "should read all selectors" $ \sels -> do
65+
sels
66+
`shouldBe` [ Selection
67+
{ selectorName = "test_user"
68+
, selectorKey =
69+
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIITaA+1gRPR3BMWGwF5ppDvQDyjqJ1VJNCQTlErA9ot"
70+
}
71+
]

cli/test/User/Requester/CliSpec.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ wallet = case readWallet (True, mnemonics) of
8484
sshClient :: SSHClient 'WithSelector
8585
sshClient =
8686
SSHClient
87-
{ sshKeySelector = "alice"
87+
{ sshKeySelector = Just "alice"
8888
, sshKeyFile = "alice_id_ed25519"
8989
, sshKeyPassphrase = "testpassphrase"
9090
}
@@ -178,7 +178,7 @@ wUKzoj1GlS881w5d1K9cXgaTNg2jXmtV3Mm/nYAZtxPXAp/9gxzUE2wWhQdi9NubBHIotl
178178
|]
179179

180180
keyPair :: KeyPair
181-
keyPair = case mkKeyAPI "testpassphrase" aliceKey "alice" of
181+
keyPair = case mkKeyAPI "testpassphrase" aliceKey (Just "alice") of
182182
Nothing -> error "Failed to create KeyPair"
183183
Just k -> k
184184

0 commit comments

Comments
 (0)