-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
backend_test.go
1775 lines (1607 loc) · 56.5 KB
/
backend_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package ssh
import (
"bytes"
"context"
"fmt"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/credential/userpass"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/sdk/logical"
"net"
"reflect"
"strconv"
"testing"
"time"
"golang.org/x/crypto/ssh"
"encoding/base64"
"encoding/json"
"errors"
"strings"
"github.com/hashicorp/vault/helper/testhelpers/docker"
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/mapstructure"
)
const (
testIP = "127.0.0.1"
testUserName = "vaultssh"
testAdminUser = "vaultssh"
testCaKeyType = "ca"
testOTPKeyType = "otp"
testDynamicKeyType = "dynamic"
testCIDRList = "127.0.0.1/32"
testAtRoleName = "test@RoleName"
testDynamicRoleName = "testDynamicRoleName"
testOTPRoleName = "testOTPRoleName"
// testKeyName is the name of the entry that will be written to SSHMOUNTPOINT/ssh/keys
testKeyName = "testKeyName"
// testSharedPrivateKey is the value of the entry that will be written to SSHMOUNTPOINT/ssh/keys
testSharedPrivateKey = `
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvYvoRcWRxqOim5VZnuM6wHCbLUeiND0yaM1tvOl+Fsrz55DG
A0OZp4RGAu1Fgr46E1mzxFz1+zY4UbcEExg+u21fpa8YH8sytSWW1FyuD8ICib0A
/l8slmDMw4BkkGOtSlEqgscpkpv/TWZD1NxJWkPcULk8z6c7TOETn2/H9mL+v2RE
mbE6NDEwJKfD3MvlpIqCP7idR+86rNBAODjGOGgyUbtFLT+K01XmDRALkV3V/nh+
GltyjL4c6RU4zG2iRyV5RHlJtkml+UzUMkzr4IQnkCC32CC/wmtoo/IsAprpcHVe
nkBn3eFQ7uND70p5n6GhN/KOh2j519JFHJyokwIDAQABAoIBAHX7VOvBC3kCN9/x
+aPdup84OE7Z7MvpX6w+WlUhXVugnmsAAVDczhKoUc/WktLLx2huCGhsmKvyVuH+
MioUiE+vx75gm3qGx5xbtmOfALVMRLopjCnJYf6EaFA0ZeQ+NwowNW7Lu0PHmAU8
Z3JiX8IwxTz14DU82buDyewO7v+cEr97AnERe3PUcSTDoUXNaoNxjNpEJkKREY6h
4hAY676RT/GsRcQ8tqe/rnCqPHNd7JGqL+207FK4tJw7daoBjQyijWuB7K5chSal
oPInylM6b13ASXuOAOT/2uSUBWmFVCZPDCmnZxy2SdnJGbsJAMl7Ma3MUlaGvVI+
Tfh1aQkCgYEA4JlNOabTb3z42wz6mz+Nz3JRwbawD+PJXOk5JsSnV7DtPtfgkK9y
6FTQdhnozGWShAvJvc+C4QAihs9AlHXoaBY5bEU7R/8UK/pSqwzam+MmxmhVDV7G
IMQPV0FteoXTaJSikhZ88mETTegI2mik+zleBpVxvfdhE5TR+lq8Br0CgYEA2AwJ
CUD5CYUSj09PluR0HHqamWOrJkKPFPwa+5eiTTCzfBBxImYZh7nXnWuoviXC0sg2
AuvCW+uZ48ygv/D8gcz3j1JfbErKZJuV+TotK9rRtNIF5Ub7qysP7UjyI7zCssVM
kuDd9LfRXaB/qGAHNkcDA8NxmHW3gpln4CFdSY8CgYANs4xwfercHEWaJ1qKagAe
rZyrMpffAEhicJ/Z65lB0jtG4CiE6w8ZeUMWUVJQVcnwYD+4YpZbX4S7sJ0B8Ydy
AhkSr86D/92dKTIt2STk6aCN7gNyQ1vW198PtaAWH1/cO2UHgHOy3ZUt5X/Uwxl9
cex4flln+1Viumts2GgsCQKBgCJH7psgSyPekK5auFdKEr5+Gc/jB8I/Z3K9+g4X
5nH3G1PBTCJYLw7hRzw8W/8oALzvddqKzEFHphiGXK94Lqjt/A4q1OdbCrhiE68D
My21P/dAKB1UYRSs9Y8CNyHCjuZM9jSMJ8vv6vG/SOJPsnVDWVAckAbQDvlTHC9t
O98zAoGAcbW6uFDkrv0XMCpB9Su3KaNXOR0wzag+WIFQRXCcoTvxVi9iYfUReQPi
oOyBJU/HMVvBfv4g+OVFLVgSwwm6owwsouZ0+D/LasbuHqYyqYqdyPJQYzWA2Y+F
+B6f4RoPdSXj24JHPg/ioRxjaj094UXJxua2yfkcecGNEuBQHSs=
-----END RSA PRIVATE KEY-----
`
// Public half of `testCAPrivateKey`, identical to how it would be fed in from a file
testCAPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDArgK0ilRRfk8E7HIsjz5l3BuxmwpDd8DHRCVfOhbZ4gOSVxjEOOqBwWGjygdboBIZwFXmwDlU6sWX0hBJAgpQz0Cjvbjxtq/NjkvATrYPgnrXUhTaEn2eQO0PsqRNSFH46SK/oJfTp0q8/WgojxWJ2L7FUV8PO8uIk49DzqAqPV7WXU63vFsjx+3WQOX/ILeQvHCvaqs3dWjjzEoDudRWCOdUqcHEOshV9azIzPrXlQVzRV3QAKl6u7pC+/Secorpwt6IHpMKoVPGiR0tMMuNOVH8zrAKzIxPGfy2WmNDpJopbXMTvSOGAqNcp49O4SKOQl9Fzfq2HEevJamKLrMB dummy@example.com
`
publicKey2 = `AAAAB3NzaC1yc2EAAAADAQABAAABAQDArgK0ilRRfk8E7HIsjz5l3BuxmwpDd8DHRCVfOhbZ4gOSVxjEOOqBwWGjygdboBIZwFXmwDlU6sWX0hBJAgpQz0Cjvbjxtq/NjkvATrYPgnrXUhTaEn2eQO0PsqRNSFH46SK/oJfTp0q8/WgojxWJ2L7FUV8PO8uIk49DzqAqPV7WXU63vFsjx+3WQOX/ILeQvHCvaqs3dWjjzEoDudRWCOdUqcHEOshV9azIzPrXlQVzRV3QAKl6u7pC+/Secorpwt6IHpMKoVPGiR0tMMuNOVH8zrAKzIxPGfy2WmNDpJopbXMTvSOGAqNcp49O4SKOQl9Fzfq2HEevJamKLrMB
`
publicKey4096 = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC54Oj4YCFDYxYv69Q9KfU6rWYtUB1eByQdUW0nXFi/vr98QUIV77sEeUVhaQzZcuCojAi/GrloW7ta0Z2DaEv5jOQMAnGpXBcqLJsz3KdrHbpvl93MPNdmNaGPU0GnUEsjBVuDVn9HdIUa8CNrxShvPu7/VqoaRHKLqphGgzFb37vi4qvnQ+5VYAO/TzyVYMD6qJX6I/9Pw8d74jCfEdOh2yGKkP7rXWOghreyIl8H2zTJKg9KoZuPq9F5M8nNt7Oi3rf+DwQiYvamzIqlDP4s5oFVTZW0E9lwWvYDpyiJnUrkQqksebBK/rcyfiFG3onb4qLo2WVWXeK3si8IhGik/TEzprScyAWIf9RviT8O+l5hTA2/c+ctn3MVCLRNfez2lKpdxCoprv1MbIcySGWblTJEcY6RA+aauVJpu7FMtRxHHtZKtMpep8cLu8GKbiP6Ifq2JXBtXtNxDeIgo2MkNoMh/NHAsACJniE/dqV/+u9HvhvgrTbJ69ell0nE4ivzA7O4kZgbR/4MHlLgLFvaqC8RrWRLY6BdFagPIMxghWha7Qw16zqoIjRnolvRzUWvSXanJVg8Z6ua1VxwgirNaAH1ivmJhUh2+4lNxCX6jmZyR3zjJsWY03gjJTairvI762opjjalF8fH6Xrs15mB14JiAlNbk6+5REQcvXlGqw== dummy@example.com`
testCAPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwK4CtIpUUX5PBOxyLI8+ZdwbsZsKQ3fAx0QlXzoW2eIDklcY
xDjqgcFho8oHW6ASGcBV5sA5VOrFl9IQSQIKUM9Ao7248bavzY5LwE62D4J611IU
2hJ9nkDtD7KkTUhR+Okiv6CX06dKvP1oKI8Vidi+xVFfDzvLiJOPQ86gKj1e1l1O
t7xbI8ft1kDl/yC3kLxwr2qrN3Vo48xKA7nUVgjnVKnBxDrIVfWsyMz615UFc0Vd
0ACperu6Qvv0nnKK6cLeiB6TCqFTxokdLTDLjTlR/M6wCsyMTxn8tlpjQ6SaKW1z
E70jhgKjXKePTuEijkJfRc36thxHryWpii6zAQIDAQABAoIBAA/DrPD8iF2KigiL
F+RRa/eFhLaJStOuTpV/G9eotwnolgY5Hguf5H/tRIHUG7oBZLm6pMyWWZp7AuOj
CjYO9q0Z5939vc349nVI+SWoyviF4msPiik1bhWulja8lPjFu/8zg+ZNy15Dx7ei
vAzleAupMiKOv8pNSB/KguQ3WZ9a9bcQcoFQ2Foru6mXpLJ03kghVRlkqvQ7t5cA
n11d2Hiipq9mleESr0c+MUPKLBX/neaWfGA4xgJTjIYjZi6avmYc/Ox3sQ9aLq2J
tH0D4HVUZvaU28hn+jhbs64rRFbu++qQMe3vNvi/Q/iqcYU4b6tgDNzm/JFRTS/W
njiz4mkCgYEA44CnQVmonN6qQ0AgNNlBY5+RX3wwBJZ1AaxpzwDRylAt2vlVUA0n
YY4RW4J4+RMRKwHwjxK5RRmHjsIJx+nrpqihW3fte3ev5F2A9Wha4dzzEHxBY6IL
362T/x2f+vYk6tV+uTZSUPHsuELH26mitbBVFNB/00nbMNdEc2bO5FMCgYEA2NCw
ubt+g2bRkkT/Qf8gIM8ZDpZbARt6onqxVcWkQFT16ZjbsBWUrH1Xi7alv9+lwYLJ
ckY/XDX4KeU19HabeAbpyy6G9Q2uBSWZlJbjl7QNhdLeuzV82U1/r8fy6Uu3gQnU
WSFx2GesRpSmZpqNKMs5ksqteZ9Yjg1EIgXdINsCgYBIn9REt1NtKGOf7kOZu1T1
cYXdvm4xuLoHW7u3OiK+e9P3mCqU0G4m5UxDMyZdFKohWZAqjCaamWi9uNGYgOMa
I7DG20TzaiS7OOIm9TY17eul8pSJMrypnealxRZB7fug/6Bhjaa/cktIEwFr7P4l
E/JFH73+fBA9yipu0H3xQwKBgHmiwrLAZF6VrVcxDD9bQQwHA5iyc4Wwg+Fpkdl7
0wUgZQHTdtRXlxwaCaZhJqX5c4WXuSo6DMvPn1TpuZZXgCsbPch2ZtJOBWXvzTSW
XkK6iaedQMWoYU2L8+mK9FU73EwxVodWgwcUSosiVCRV6oGLWdZnjGEiK00uVh38
Si1nAoGBAL47wWinv1cDTnh5mm0mybz3oI2a6V9aIYCloQ/EFcvtahyR/gyB8qNF
lObH9Faf0WGdnACZvTz22U9gWhw79S0SpDV31tC5Kl8dXHFiZ09vYUKkYmSd/kms
SeKWrUkryx46LVf6NMhkyYmRqCEjBwfOozzezi5WbiJy6nn54GQt
-----END RSA PRIVATE KEY-----
`
testCAPublicKeyEd25519 = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1S6g5Bib7vT8eoFnvTl3dZSjOQL/GkH1nkRcDS9++a ca
`
testCAPrivateKeyEd25519 = `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDtUuoOQYm+70/HqBZ705d3WUozkC/xpB9Z5EXA0vfvmgAAAIhfRuszX0br
MwAAAAtzc2gtZWQyNTUxOQAAACDtUuoOQYm+70/HqBZ705d3WUozkC/xpB9Z5EXA0vfvmg
AAAEBQYa029SP/7AGPFQLmzwOc9eCoOZuwCq3iIf2C6fj9j+1S6g5Bib7vT8eoFnvTl3dZ
SjOQL/GkH1nkRcDS9++aAAAAAmNhAQID
-----END OPENSSH PRIVATE KEY-----
`
// testPublicKeyInstall is the public key that is installed in the
// admin account's authorized_keys
testPublicKeyInstall = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9i+hFxZHGo6KblVme4zrAcJstR6I0PTJozW286X4WyvPnkMYDQ5mnhEYC7UWCvjoTWbPEXPX7NjhRtwQTGD67bV+lrxgfyzK1JZbUXK4PwgKJvQD+XyyWYMzDgGSQY61KUSqCxymSm/9NZkPU3ElaQ9xQuTzPpztM4ROfb8f2Yv6/ZESZsTo0MTAkp8Pcy+WkioI/uJ1H7zqs0EA4OMY4aDJRu0UtP4rTVeYNEAuRXdX+eH4aW3KMvhzpFTjMbaJHJXlEeUm2SaX5TNQyTOvghCeQILfYIL/Ca2ij8iwCmulwdV6eQGfd4VDu40PvSnmfoaE38o6HaPnX0kUcnKiT"
dockerImageTagSupportsRSA1 = "8.1_p1-r0-ls20"
dockerImageTagSupportsNoRSA1 = "8.3_p1-r0-ls21"
)
func prepareTestContainer(t *testing.T, tag, caPublicKeyPEM string) (func(), string) {
if tag == "" {
tag = dockerImageTagSupportsNoRSA1
}
runner, err := docker.NewServiceRunner(docker.RunOptions{
ContainerName: "openssh",
ImageRepo: "linuxserver/openssh-server",
ImageTag: tag,
Env: []string{
"DOCKER_MODS=linuxserver/mods:openssh-server-openssh-client",
"PUBLIC_KEY=" + testPublicKeyInstall,
"SUDO_ACCESS=true",
"USER_NAME=vaultssh",
},
Ports: []string{"2222/tcp"},
})
if err != nil {
t.Fatalf("Could not start local ssh docker container: %s", err)
}
svc, err := runner.StartService(context.Background(), func(ctx context.Context, host string, port int) (docker.ServiceConfig, error) {
ipaddr, err := net.ResolveIPAddr("ip", host)
if err != nil {
return nil, err
}
sshAddress := fmt.Sprintf("%s:%d", ipaddr.String(), port)
signer, err := ssh.ParsePrivateKey([]byte(testSharedPrivateKey))
if err != nil {
return nil, err
}
// Install util-linux for non-busybox flock that supports timeout option
err = testSSH("vaultssh", sshAddress, ssh.PublicKeys(signer), fmt.Sprintf(`
set -e;
sudo ln -s /config /home/vaultssh
sudo apk add util-linux;
echo "LogLevel DEBUG" | sudo tee -a /config/ssh_host_keys/sshd_config;
echo "TrustedUserCAKeys /config/ssh_host_keys/trusted-user-ca-keys.pem" | sudo tee -a /config/ssh_host_keys/sshd_config;
kill -HUP $(cat /config/sshd.pid)
echo "%s" | sudo tee /config/ssh_host_keys/trusted-user-ca-keys.pem
`, caPublicKeyPEM))
if err != nil {
return nil, err
}
return docker.NewServiceHostPort(ipaddr.String(), port), nil
})
if err != nil {
t.Fatalf("Could not start docker ssh server: %s", err)
}
return svc.Cleanup, svc.Config.Address()
}
func testSSH(user, host string, auth ssh.AuthMethod, command string) error {
client, err := ssh.Dial("tcp", host, &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{auth},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 5 * time.Second,
})
if err != nil {
return fmt.Errorf("unable to dial sshd to host %q: %v", host, err)
}
session, err := client.NewSession()
if err != nil {
return fmt.Errorf("unable to create sshd session to host %q: %v", host, err)
}
var stderr bytes.Buffer
session.Stderr = &stderr
defer session.Close()
err = session.Run(command)
if err != nil {
return fmt.Errorf("command %v failed, error: %v, stderr: %v", command, err, stderr.String())
}
return err
}
func TestBackend_allowed_users(t *testing.T) {
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b, err := Backend(config)
if err != nil {
t.Fatal(err)
}
err = b.Setup(context.Background(), config)
if err != nil {
t.Fatal(err)
}
roleData := map[string]interface{}{
"key_type": "otp",
"default_user": "ubuntu",
"cidr_list": "52.207.235.245/16",
"allowed_users": "test",
}
roleReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/role1",
Storage: config.StorageView,
Data: roleData,
}
resp, err := b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) || resp != nil {
t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
}
credsData := map[string]interface{}{
"ip": "52.207.235.245",
"username": "ubuntu",
}
credsReq := &logical.Request{
Operation: logical.UpdateOperation,
Storage: config.StorageView,
Path: "creds/role1",
Data: credsData,
}
resp, err = b.HandleRequest(context.Background(), credsReq)
if err != nil || (resp != nil && resp.IsError()) || resp == nil {
t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
}
if resp.Data["key"] == "" ||
resp.Data["key_type"] != "otp" ||
resp.Data["ip"] != "52.207.235.245" ||
resp.Data["username"] != "ubuntu" {
t.Fatalf("failed to create credential: resp:%#v", resp)
}
credsData["username"] = "test"
resp, err = b.HandleRequest(context.Background(), credsReq)
if err != nil || (resp != nil && resp.IsError()) || resp == nil {
t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
}
if resp.Data["key"] == "" ||
resp.Data["key_type"] != "otp" ||
resp.Data["ip"] != "52.207.235.245" ||
resp.Data["username"] != "test" {
t.Fatalf("failed to create credential: resp:%#v", resp)
}
credsData["username"] = "random"
resp, err = b.HandleRequest(context.Background(), credsReq)
if err != nil || resp == nil || (resp != nil && !resp.IsError()) {
t.Fatalf("expected failure: resp:%#v err:%s", resp, err)
}
delete(roleData, "allowed_users")
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) || resp != nil {
t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
}
credsData["username"] = "ubuntu"
resp, err = b.HandleRequest(context.Background(), credsReq)
if err != nil || (resp != nil && resp.IsError()) || resp == nil {
t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
}
if resp.Data["key"] == "" ||
resp.Data["key_type"] != "otp" ||
resp.Data["ip"] != "52.207.235.245" ||
resp.Data["username"] != "ubuntu" {
t.Fatalf("failed to create credential: resp:%#v", resp)
}
credsData["username"] = "test"
resp, err = b.HandleRequest(context.Background(), credsReq)
if err != nil || resp == nil || (resp != nil && !resp.IsError()) {
t.Fatalf("expected failure: resp:%#v err:%s", resp, err)
}
roleData["allowed_users"] = "*"
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) || resp != nil {
t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
}
resp, err = b.HandleRequest(context.Background(), credsReq)
if err != nil || (resp != nil && resp.IsError()) || resp == nil {
t.Fatalf("failed to create role: resp:%#v err:%s", resp, err)
}
if resp.Data["key"] == "" ||
resp.Data["key_type"] != "otp" ||
resp.Data["ip"] != "52.207.235.245" ||
resp.Data["username"] != "test" {
t.Fatalf("failed to create credential: resp:%#v", resp)
}
}
func TestBackend_allowed_users_template(t *testing.T) {
testEntityID := "c65c8cff-5142-3d41-93d0-9d06286e7203"
testSshUsername := "testsshusername"
testEntityMetadata := map[string]string{
"ssh_username": testSshUsername,
}
testAllowedUsersTemplate := "ssh-{{ identity.entity.metadata.ssh_username }}"
expectedValidPrincipal := "ssh-" + testSshUsername
// Setup Vault cluster with a preexisting entity.
coreConfig := &vault.CoreConfig{
CredentialBackends: map[string]logical.Factory{
"userpass": userpass.Factory,
},
LogicalBackends: map[string]logical.Factory{
"ssh": func(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
defaultLeaseTTLVal := 2 * time.Minute
maxLeaseTTLVal := 10 * time.Minute
return Factory(context.Background(), &logical.BackendConfig{
Logger: vault.NewTestLogger(t),
StorageView: &logical.InmemStorage{},
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: defaultLeaseTTLVal,
MaxLeaseTTLVal: maxLeaseTTLVal,
EntityVal: &logical.Entity{
ID: testEntityID,
Name: "testentity",
Aliases: []*logical.Alias{},
Metadata: testEntityMetadata,
},
},
})
},
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
core := cluster.Cores[0]
client := core.Client
vault.TestWaitActive(t, core.Core)
// Create a policy allowing our test user to sign a SSH key.
err := client.Sys().PutPolicy("test", `
path "ssh/*" {
capabilities = ["update"]
}
`)
if err != nil {
t.Fatal(err)
}
// Init userpass auth with test user.
if err := client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
Type: "userpass",
}); err != nil {
t.Fatal(err)
}
if _, err := client.Logical().Write("auth/userpass/users/userpassname", map[string]interface{}{
"password": "test",
"policies": "test",
}); err != nil {
t.Fatal(err)
}
// Init SSH secret engine with a signing role.
err = client.Sys().Mount("ssh", &api.MountInput{
Type: "ssh",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "60h",
},
})
if err != nil {
t.Fatal(err)
}
_, err = client.Logical().Write("ssh/config/ca", map[string]interface{}{
"generate_signing_key": true,
})
if err != nil {
t.Fatal(err)
}
_, err = client.Logical().Write("ssh/roles/my-role", map[string]interface{}{
"key_type": testCaKeyType,
"allow_user_certificates": true,
"allowed_users": testAllowedUsersTemplate,
"allowed_users_template": true,
})
if err != nil {
t.Fatal(err)
}
// Login test user and sign key with expected valid principal.
secret, err := client.Logical().Write("auth/userpass/login/userpassname", map[string]interface{}{
"password": "test",
})
if err != nil || secret == nil {
t.Fatal(err)
}
userpassToken := secret.Auth.ClientToken
client.SetToken(userpassToken)
_, err = client.Logical().Write("ssh/sign/my-role", map[string]interface{}{
"public_key": testCAPublicKey,
"valid_principals": expectedValidPrincipal,
})
if err != nil {
t.Fatal(err)
}
}
func newTestingFactory(t *testing.T) func(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
return func(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
defaultLeaseTTLVal := 2 * time.Minute
maxLeaseTTLVal := 10 * time.Minute
return Factory(context.Background(), &logical.BackendConfig{
Logger: vault.NewTestLogger(t),
StorageView: &logical.InmemStorage{},
System: &logical.StaticSystemView{
DefaultLeaseTTLVal: defaultLeaseTTLVal,
MaxLeaseTTLVal: maxLeaseTTLVal,
},
})
}
}
func TestSSHBackend_Lookup(t *testing.T) {
testOTPRoleData := map[string]interface{}{
"key_type": testOTPKeyType,
"default_user": testUserName,
"cidr_list": testCIDRList,
}
testDynamicRoleData := map[string]interface{}{
"key_type": testDynamicKeyType,
"key": testKeyName,
"admin_user": testAdminUser,
"default_user": testAdminUser,
"cidr_list": testCIDRList,
}
data := map[string]interface{}{
"ip": testIP,
}
resp1 := []string(nil)
resp2 := []string{testOTPRoleName}
resp3 := []string{testDynamicRoleName, testOTPRoleName}
resp4 := []string{testDynamicRoleName}
resp5 := []string{testAtRoleName}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testLookupRead(t, data, resp1),
testRoleWrite(t, testOTPRoleName, testOTPRoleData),
testLookupRead(t, data, resp2),
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testRoleWrite(t, testDynamicRoleName, testDynamicRoleData),
testLookupRead(t, data, resp3),
testRoleDelete(t, testOTPRoleName),
testLookupRead(t, data, resp4),
testRoleDelete(t, testDynamicRoleName),
testLookupRead(t, data, resp1),
testRoleWrite(t, testAtRoleName, testDynamicRoleData),
testLookupRead(t, data, resp5),
testRoleDelete(t, testAtRoleName),
testLookupRead(t, data, resp1),
},
})
}
func TestSSHBackend_RoleList(t *testing.T) {
testOTPRoleData := map[string]interface{}{
"key_type": testOTPKeyType,
"default_user": testUserName,
"cidr_list": testCIDRList,
}
resp1 := map[string]interface{}{}
resp2 := map[string]interface{}{
"keys": []string{testOTPRoleName},
"key_info": map[string]interface{}{
testOTPRoleName: map[string]interface{}{
"key_type": testOTPKeyType,
},
},
}
resp3 := map[string]interface{}{
"keys": []string{testAtRoleName, testOTPRoleName},
"key_info": map[string]interface{}{
testOTPRoleName: map[string]interface{}{
"key_type": testOTPKeyType,
},
testAtRoleName: map[string]interface{}{
"key_type": testOTPKeyType,
},
},
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testRoleList(t, resp1),
testRoleWrite(t, testOTPRoleName, testOTPRoleData),
testRoleList(t, resp2),
testRoleWrite(t, testAtRoleName, testOTPRoleData),
testRoleList(t, resp3),
testRoleDelete(t, testAtRoleName),
testRoleList(t, resp2),
testRoleDelete(t, testOTPRoleName),
testRoleList(t, resp1),
},
})
}
func TestSSHBackend_DynamicKeyCreate(t *testing.T) {
cleanup, sshAddress := prepareTestContainer(t, "", "")
defer cleanup()
host, port, err := net.SplitHostPort(sshAddress)
if err != nil {
t.Fatal(err)
}
testDynamicRoleData := map[string]interface{}{
"key_type": testDynamicKeyType,
"key": testKeyName,
"admin_user": testAdminUser,
"default_user": testAdminUser,
"cidr_list": host + "/32",
"port": port,
}
data := map[string]interface{}{
"username": testUserName,
"ip": host,
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testRoleWrite(t, testDynamicRoleName, testDynamicRoleData),
testCredsWrite(t, testDynamicRoleName, data, false, sshAddress),
testRoleWrite(t, testAtRoleName, testDynamicRoleData),
testCredsWrite(t, testAtRoleName, data, false, sshAddress),
},
})
}
func TestSSHBackend_OTPRoleCrud(t *testing.T) {
testOTPRoleData := map[string]interface{}{
"key_type": testOTPKeyType,
"default_user": testUserName,
"cidr_list": testCIDRList,
}
respOTPRoleData := map[string]interface{}{
"key_type": testOTPKeyType,
"port": 22,
"default_user": testUserName,
"cidr_list": testCIDRList,
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testRoleWrite(t, testOTPRoleName, testOTPRoleData),
testRoleRead(t, testOTPRoleName, respOTPRoleData),
testRoleDelete(t, testOTPRoleName),
testRoleRead(t, testOTPRoleName, nil),
testRoleWrite(t, testAtRoleName, testOTPRoleData),
testRoleRead(t, testAtRoleName, respOTPRoleData),
testRoleDelete(t, testAtRoleName),
testRoleRead(t, testAtRoleName, nil),
},
})
}
func TestSSHBackend_DynamicRoleCrud(t *testing.T) {
testDynamicRoleData := map[string]interface{}{
"key_type": testDynamicKeyType,
"key": testKeyName,
"admin_user": testAdminUser,
"default_user": testAdminUser,
"cidr_list": testCIDRList,
}
respDynamicRoleData := map[string]interface{}{
"cidr_list": testCIDRList,
"port": 22,
"install_script": DefaultPublicKeyInstallScript,
"key_bits": 1024,
"key": testKeyName,
"admin_user": testUserName,
"default_user": testUserName,
"key_type": testDynamicKeyType,
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testRoleWrite(t, testDynamicRoleName, testDynamicRoleData),
testRoleRead(t, testDynamicRoleName, respDynamicRoleData),
testRoleDelete(t, testDynamicRoleName),
testRoleRead(t, testDynamicRoleName, nil),
testRoleWrite(t, testAtRoleName, testDynamicRoleData),
testRoleRead(t, testAtRoleName, respDynamicRoleData),
testRoleDelete(t, testAtRoleName),
testRoleRead(t, testAtRoleName, nil),
},
})
}
func TestSSHBackend_NamedKeysCrud(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testNamedKeysDelete(t),
},
})
}
func TestSSHBackend_OTPCreate(t *testing.T) {
cleanup, sshAddress := prepareTestContainer(t, "", "")
defer func() {
if !t.Failed() {
cleanup()
}
}()
host, port, err := net.SplitHostPort(sshAddress)
if err != nil {
t.Fatal(err)
}
testOTPRoleData := map[string]interface{}{
"key_type": testOTPKeyType,
"default_user": testUserName,
"cidr_list": host + "/32",
"port": port,
}
data := map[string]interface{}{
"username": testUserName,
"ip": host,
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testRoleWrite(t, testOTPRoleName, testOTPRoleData),
testCredsWrite(t, testOTPRoleName, data, false, sshAddress),
},
})
}
func TestSSHBackend_VerifyEcho(t *testing.T) {
verifyData := map[string]interface{}{
"otp": api.VerifyEchoRequest,
}
expectedData := map[string]interface{}{
"message": api.VerifyEchoResponse,
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testVerifyWrite(t, verifyData, expectedData),
},
})
}
func TestSSHBackend_ConfigZeroAddressCRUD(t *testing.T) {
testOTPRoleData := map[string]interface{}{
"key_type": testOTPKeyType,
"default_user": testUserName,
"cidr_list": testCIDRList,
}
testDynamicRoleData := map[string]interface{}{
"key_type": testDynamicKeyType,
"key": testKeyName,
"admin_user": testAdminUser,
"default_user": testAdminUser,
"cidr_list": testCIDRList,
}
req1 := map[string]interface{}{
"roles": testOTPRoleName,
}
resp1 := map[string]interface{}{
"roles": []string{testOTPRoleName},
}
req2 := map[string]interface{}{
"roles": fmt.Sprintf("%s,%s", testOTPRoleName, testDynamicRoleName),
}
resp2 := map[string]interface{}{
"roles": []string{testOTPRoleName, testDynamicRoleName},
}
resp3 := map[string]interface{}{
"roles": []string{},
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testRoleWrite(t, testOTPRoleName, testOTPRoleData),
testConfigZeroAddressWrite(t, req1),
testConfigZeroAddressRead(t, resp1),
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testRoleWrite(t, testDynamicRoleName, testDynamicRoleData),
testConfigZeroAddressWrite(t, req2),
testConfigZeroAddressRead(t, resp2),
testRoleDelete(t, testDynamicRoleName),
testConfigZeroAddressRead(t, resp1),
testRoleDelete(t, testOTPRoleName),
testConfigZeroAddressRead(t, resp3),
testConfigZeroAddressDelete(t),
},
})
}
func TestSSHBackend_CredsForZeroAddressRoles_otp(t *testing.T) {
otpRoleData := map[string]interface{}{
"key_type": testOTPKeyType,
"default_user": testUserName,
}
data := map[string]interface{}{
"username": testUserName,
"ip": testIP,
}
req1 := map[string]interface{}{
"roles": testOTPRoleName,
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testRoleWrite(t, testOTPRoleName, otpRoleData),
testCredsWrite(t, testOTPRoleName, data, true, ""),
testConfigZeroAddressWrite(t, req1),
testCredsWrite(t, testOTPRoleName, data, false, ""),
testConfigZeroAddressDelete(t),
testCredsWrite(t, testOTPRoleName, data, true, ""),
},
})
}
func TestSSHBackend_CredsForZeroAddressRoles_dynamic(t *testing.T) {
cleanup, sshAddress := prepareTestContainer(t, "", "")
defer cleanup()
host, port, err := net.SplitHostPort(sshAddress)
if err != nil {
t.Fatal(err)
}
dynamicRoleData := map[string]interface{}{
"key_type": testDynamicKeyType,
"key": testKeyName,
"admin_user": testAdminUser,
"default_user": testAdminUser,
"port": port,
}
data := map[string]interface{}{
"username": testUserName,
"ip": host,
}
req2 := map[string]interface{}{
"roles": testDynamicRoleName,
}
logicaltest.Test(t, logicaltest.TestCase{
LogicalFactory: newTestingFactory(t),
Steps: []logicaltest.TestStep{
testNamedKeysWrite(t, testKeyName, testSharedPrivateKey),
testRoleWrite(t, testDynamicRoleName, dynamicRoleData),
testCredsWrite(t, testDynamicRoleName, data, true, sshAddress),
testConfigZeroAddressWrite(t, req2),
testCredsWrite(t, testDynamicRoleName, data, false, sshAddress),
testConfigZeroAddressDelete(t),
testCredsWrite(t, testDynamicRoleName, data, true, sshAddress),
},
})
}
func TestSSHBackend_CA(t *testing.T) {
testCases := []struct {
name string
tag string
caPublicKey string
caPrivateKey string
algoSigner string
expectError bool
}{
{
"RSAKey_EmptyAlgoSigner_ImageSupportsRSA1",
dockerImageTagSupportsRSA1,
testCAPublicKey,
testCAPrivateKey,
"",
false,
},
{
"RSAKey_EmptyAlgoSigner_ImageSupportsNoRSA1",
dockerImageTagSupportsNoRSA1,
testCAPublicKey,
testCAPrivateKey,
"",
true,
},
{
"RSAKey_RSA1AlgoSigner_ImageSupportsRSA1",
dockerImageTagSupportsRSA1,
testCAPublicKey,
testCAPrivateKey,
ssh.SigAlgoRSA,
false,
},
{
"RSAKey_RSASHA2256AlgoSigner_ImageSupportsRSA1",
dockerImageTagSupportsRSA1,
testCAPublicKey,
testCAPrivateKey,
ssh.SigAlgoRSASHA2256,
false,
},
{
"RSAKey_RSASHA2256AlgoSigner_ImageSupportsNoRSA1",
dockerImageTagSupportsNoRSA1,
testCAPublicKey,
testCAPrivateKey,
ssh.SigAlgoRSASHA2256,
false,
},
{
"ed25519Key_EmptyAlgoSigner_ImageSupportsRSA1",
dockerImageTagSupportsRSA1,
testCAPublicKeyEd25519,
testCAPrivateKeyEd25519,
"",
false,
},
{
"ed25519Key_EmptyAlgoSigner_ImageSupportsNoRSA1",
dockerImageTagSupportsNoRSA1,
testCAPublicKeyEd25519,
testCAPrivateKeyEd25519,
"",
false,
},
{
"ed25519Key_RSA1AlgoSigner_ImageSupportsRSA1",
dockerImageTagSupportsRSA1,
testCAPublicKeyEd25519,
testCAPrivateKeyEd25519,
ssh.SigAlgoRSA,
true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
testSSHBackend_CA(t, tc.tag, tc.caPublicKey, tc.caPrivateKey, tc.algoSigner, tc.expectError)
})
}
}
func testSSHBackend_CA(t *testing.T, dockerImageTag, caPublicKey, caPrivateKey, algorithmSigner string, expectError bool) {
cleanup, sshAddress := prepareTestContainer(t, dockerImageTag, caPublicKey)
defer cleanup()
config := logical.TestBackendConfig()
b, err := Factory(context.Background(), config)
if err != nil {
t.Fatalf("Cannot create backend: %s", err)
}
testKeyToSignPrivate := `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEAwn1V2xd/EgJXIY53fBTtc20k/ajekqQngvkpFSwNHW63XNEQK8Ll
FOCyGXoje9DUGxnYs3F/ohfsBBWkLNfU7fiENdSJL1pbkAgJ+2uhV9sLZjvYhikrXWoyJX
LDKfY12LjpcBS2HeLMT04laZ/xSJrOBEJHGzHyr2wUO0NUQUQPUODAFhnHKgvvA4Uu79UY
gcdThF4w83+EAnE4JzBZMKPMjzy4u1C0R/LoD8DuapHwX6NGWdEUvUZZ+XRcIWeCOvR0ne
qGBRH35k1Mv7k65d7kkE0uvM5Z36erw3tdoszxPYf7AKnO1DpeU2uwMcym6xNwfwynKjhL
qL/Mgi4uRwAAA8iAsY0zgLGNMwAAAAdzc2gtcnNhAAABAQDCfVXbF38SAlchjnd8FO1zbS
T9qN6SpCeC+SkVLA0dbrdc0RArwuUU4LIZeiN70NQbGdizcX+iF+wEFaQs19Tt+IQ11Ikv
WluQCAn7a6FX2wtmO9iGKStdajIlcsMp9jXYuOlwFLYd4sxPTiVpn/FIms4EQkcbMfKvbB
Q7Q1RBRA9Q4MAWGccqC+8DhS7v1RiBx1OEXjDzf4QCcTgnMFkwo8yPPLi7ULRH8ugPwO5q
kfBfo0ZZ0RS9Rln5dFwhZ4I69HSd6oYFEffmTUy/uTrl3uSQTS68zlnfp6vDe12izPE9h/
sAqc7UOl5Ta7AxzKbrE3B/DKcqOEuov8yCLi5HAAAAAwEAAQAAAQABns2yT5XNbpuPOgKg
1APObGBchKWmDxwNKUpAVOefEScR7OP3mV4TOHQDZlMZWvoJZ8O4av+nOA/NUOjXPs0VVn
azhBvIezY8EvUSVSk49Cg6J9F7/KfR1WqpiTU7CkQUlCXNuz5xLUyKdJo3MQ/vjOqeenbh
MR9Wes4IWF1BVe4VOD6lxRsjwuIieIgmScW28FFh2rgsEfO2spzZ3AWOGExw+ih757hFz5
4A2fhsQXP8m3r8m7iiqcjTLWXdxTUk4zot2kZEjbI4Avk0BL+wVeFq6f/y+G+g5edqSo7j
uuSgzbUQtA9PMnGxhrhU2Ob7n3VGdya7WbGZkaKP8zJhAAAAgQC3bJurmOSLIi3KVhp7lD
/FfxwXHwVBFALCgq7EyNlkTz6RDoMFM4eOTRMDvsgWxT+bSB8R8eg1sfgY8rkHOuvTAVI5
3oEYco3H7NWE9X8Zt0lyhO1uaE49EENNSQ8hY7R3UIw5becyI+7ZZxs9HkBgCQCZzSjzA+
SIyAoMKM261AAAAIEA+PCkcDRp3J0PaoiuetXSlWZ5WjP3CtwT2xrvEX9x+ZsDgXCDYQ5T
osxvEKOGSfIrHUUhzZbFGvqWyfrziPe9ypJrtCM7RJT/fApBXnbWFcDZzWamkQvohst+0w
XHYCmNoJ6/Y+roLv3pzyFUmqRNcrQaohex7TZmsvHJT513UakAAACBAMgBXxH8DyNYdniX
mIXEto4GqMh4rXdNwCghfpyWdJE6vCyDt7g7bYMq7AQ2ynSKRtQDT/ZgQNfSbilUq3iXz7
xNZn5U9ndwFs90VmEpBup/PmhfX+Gwt5hQZLbkKZcgQ9XrhSKdMxVm1yy/fk0U457enlz5
cKumubUxOfFdy1ZvAAAAEm5jY0BtYnAudWJudC5sb2NhbA==
-----END OPENSSH PRIVATE KEY-----
`
testKeyToSignPublic := `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCfVXbF38SAlchjnd8FO1zbST9qN6SpCeC+SkVLA0dbrdc0RArwuUU4LIZeiN70NQbGdizcX+iF+wEFaQs19Tt+IQ11IkvWluQCAn7a6FX2wtmO9iGKStdajIlcsMp9jXYuOlwFLYd4sxPTiVpn/FIms4EQkcbMfKvbBQ7Q1RBRA9Q4MAWGccqC+8DhS7v1RiBx1OEXjDzf4QCcTgnMFkwo8yPPLi7ULRH8ugPwO5qkfBfo0ZZ0RS9Rln5dFwhZ4I69HSd6oYFEffmTUy/uTrl3uSQTS68zlnfp6vDe12izPE9h/sAqc7UOl5Ta7AxzKbrE3B/DKcqOEuov8yCLi5H `
roleOptions := map[string]interface{}{
"allow_user_certificates": true,
"allowed_users": "*",
"default_extensions": []map[string]string{
{
"permit-pty": "",
},
},
"key_type": "ca",
"default_user": testUserName,
"ttl": "30m0s",
}
if algorithmSigner != "" {
roleOptions["algorithm_signer"] = algorithmSigner
}
testCase := logicaltest.TestCase{
LogicalBackend: b,
Steps: []logicaltest.TestStep{
configCaStep(caPublicKey, caPrivateKey),
testRoleWrite(t, "testcarole", roleOptions),
logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "sign/testcarole",
ErrorOk: expectError,
Data: map[string]interface{}{
"public_key": testKeyToSignPublic,
"valid_principals": testUserName,
},
Check: func(resp *logical.Response) error {
// Tolerate nil response if an error was expected
if expectError && resp == nil {
return nil
}
signedKey := strings.TrimSpace(resp.Data["signed_key"].(string))
if signedKey == "" {
return errors.New("no signed key in response")
}
privKey, err := ssh.ParsePrivateKey([]byte(testKeyToSignPrivate))
if err != nil {
return fmt.Errorf("error parsing private key: %v", err)
}
parsedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(signedKey))
if err != nil {
return fmt.Errorf("error parsing signed key: %v", err)
}
certSigner, err := ssh.NewCertSigner(parsedKey.(*ssh.Certificate), privKey)
if err != nil {
return err
}
err = testSSH(testUserName, sshAddress, ssh.PublicKeys(certSigner), "date")
if expectError && err == nil {
return fmt.Errorf("expected error but got none")
}
if !expectError && err != nil {
return err
}
return nil
},
},
},
}
logicaltest.Test(t, testCase)
}
func TestSSHBackend_CAUpgradeAlgorithmSigner(t *testing.T) {
cleanup, sshAddress := prepareTestContainer(t, dockerImageTagSupportsRSA1, testCAPublicKey)
defer cleanup()
config := logical.TestBackendConfig()
b, err := Factory(context.Background(), config)
if err != nil {
t.Fatalf("Cannot create backend: %s", err)
}
testKeyToSignPrivate := `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEAwn1V2xd/EgJXIY53fBTtc20k/ajekqQngvkpFSwNHW63XNEQK8Ll
FOCyGXoje9DUGxnYs3F/ohfsBBWkLNfU7fiENdSJL1pbkAgJ+2uhV9sLZjvYhikrXWoyJX