diff --git a/go-ssh-keysign/go.mod b/go-ssh-keysign/go.mod
index 4600e8a..84d2921 100644
--- a/go-ssh-keysign/go.mod
+++ b/go-ssh-keysign/go.mod
@@ -1,39 +1,35 @@
module binarycodes/ssh-keysign
-go 1.24
+go 1.24.0
require (
github.com/mdp/qrterminal/v3 v3.2.1
github.com/mitchellh/mapstructure v1.5.0
- github.com/spf13/cobra v1.8.1
- github.com/spf13/pflag v1.0.5
- github.com/spf13/viper v1.19.0
- github.com/stretchr/testify v1.9.0
- go.uber.org/zap v1.21.0
- golang.org/x/crypto v0.21.0
+ github.com/spf13/cobra v1.10.1
+ github.com/spf13/pflag v1.0.10
+ github.com/spf13/viper v1.21.0
+ github.com/stretchr/testify v1.11.1
+ go.uber.org/zap v1.27.0
+ golang.org/x/crypto v0.43.0
)
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
- github.com/fsnotify/fsnotify v1.7.0 // indirect
- github.com/hashicorp/hcl v1.0.0 // indirect
+ github.com/fsnotify/fsnotify v1.9.0 // indirect
+ github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/magiconair/properties v1.8.7 // indirect
- github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
- github.com/sagikazarmark/locafero v0.4.0 // indirect
- github.com/sagikazarmark/slog-shim v0.1.0 // indirect
- github.com/sourcegraph/conc v0.3.0 // indirect
- github.com/spf13/afero v1.11.0 // indirect
- github.com/spf13/cast v1.6.0 // indirect
+ github.com/sagikazarmark/locafero v0.12.0 // indirect
+ github.com/spf13/afero v1.15.0 // indirect
+ github.com/spf13/cast v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
- go.uber.org/atomic v1.9.0 // indirect
- go.uber.org/multierr v1.9.0 // indirect
- golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
- golang.org/x/sys v0.29.0 // indirect
- golang.org/x/term v0.18.0 // indirect
- golang.org/x/text v0.14.0 // indirect
- gopkg.in/ini.v1 v1.67.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ go.yaml.in/yaml/v3 v3.0.4 // indirect
+ golang.org/x/sys v0.37.0 // indirect
+ golang.org/x/term v0.36.0 // indirect
+ golang.org/x/text v0.30.0 // indirect
+ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/qr v0.2.0 // indirect
)
diff --git a/go-ssh-keysign/go.sum b/go-ssh-keysign/go.sum
index af70f5b..583d1f4 100644
--- a/go-ssh-keysign/go.sum
+++ b/go-ssh-keysign/go.sum
@@ -1,129 +1,67 @@
-github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
-github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
-github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
-github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
-github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
+github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
-github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
-github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
+github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
-github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
-github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
-github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
-github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
-github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
-github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
-github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
-github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
-github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
-github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
-github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
-github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
-github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
+github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
+github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
+github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
+github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
+github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
+github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
+github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
+github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
+github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
-go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
-go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
-go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
-go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
-go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
-go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
-go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
-golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
-golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
-golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
-golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
+golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
+golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
+golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
+golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
+golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
+golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
-gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
diff --git a/go-ssh-keysign/host_config.sample.yml b/go-ssh-keysign/host_config.sample.yml
index 4fdea6e..406d77e 100644
--- a/go-ssh-keysign/host_config.sample.yml
+++ b/go-ssh-keysign/host_config.sample.yml
@@ -16,7 +16,6 @@ user:
host:
key: "testdata/id.pub"
principal:
- - ip-10-0-1-23.ec2.internal
- - web
+ - my-test-client
duration: 3600
diff --git a/go-ssh-keysign/internal/apperror/errors.go b/go-ssh-keysign/internal/apperror/errors.go
index f83fb67..f35e69e 100644
--- a/go-ssh-keysign/internal/apperror/errors.go
+++ b/go-ssh-keysign/internal/apperror/errors.go
@@ -21,6 +21,7 @@ const (
KFileSystem // read/write perms
KCanceled // context canceled/deadline
KHttp // http errors
+ KCert // cert/keys error
)
type appError struct {
@@ -43,6 +44,8 @@ func (kind Kind) ExitCode() int {
return 13
case KHttp:
return 14
+ case KCert:
+ return 15
default:
return 1
}
@@ -80,6 +83,10 @@ func ErrFileSystem(err error) error {
return &appError{Type: KFileSystem, OpError: err}
}
+func ErrCert(err error) error {
+ return &appError{Type: KCert, OpError: err}
+}
+
func ErrHTTP(resp *http.Response) error {
body, err := io.ReadAll(resp.Body)
if err != nil {
diff --git a/go-ssh-keysign/internal/cli/testutil/helper.go b/go-ssh-keysign/internal/cli/testutil/helper.go
index 0dfc105..dde95b8 100644
--- a/go-ssh-keysign/internal/cli/testutil/helper.go
+++ b/go-ssh-keysign/internal/cli/testutil/helper.go
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
+ "net"
"os"
"path/filepath"
"testing"
@@ -13,6 +14,7 @@ import (
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
+ "golang.org/x/crypto/ssh/agent"
"binarycodes/ssh-keysign/internal/ctxkeys"
"binarycodes/ssh-keysign/internal/logging"
@@ -40,6 +42,51 @@ func ExecuteCommand(t *testing.T, cmd *cobra.Command, args ...string) (stoutStr,
cmd.SetErr(&stderr)
cmd.SetArgs(args)
+ sockPath := t.TempDir() + "/agent.sock"
+ l, err := net.Listen("unix", sockPath)
+ if err != nil {
+ t.Fatalf("listen unix: %v", err)
+ }
+
+ defer func() {
+ if err := l.Close(); err != nil {
+ t.Fatalf("close listener: %v", err)
+ }
+ }()
+
+ // create in-memory agent keyring
+ keyring := agent.NewKeyring()
+
+ // serve connections
+ go func() {
+ for {
+ c, err := l.Accept()
+ if err != nil {
+ if err := l.Close(); err != nil {
+ return
+ }
+ return
+ }
+ go func() {
+ // handles a single connection until EOF.
+ if serveErr := agent.ServeAgent(keyring, c); serveErr != nil {
+ _ = serveErr
+ return
+ }
+ }()
+ }
+ }()
+
+ oldSock := os.Getenv("SSH_AUTH_SOCK")
+ if err := os.Setenv("SSH_AUTH_SOCK", sockPath); err != nil {
+ t.Fatalf("setenv: %v", err)
+ }
+ defer func() {
+ if err := os.Setenv("SSH_AUTH_SOCK", oldSock); err != nil {
+ t.Logf("restore SSH_AUTH_SOCK: %v", err)
+ }
+ }()
+
err = cmd.Execute()
logs = observed.All()
return stdout.String(), stderr.String(), logs, err
diff --git a/go-ssh-keysign/internal/config/model.go b/go-ssh-keysign/internal/config/model.go
index 3555238..44337eb 100644
--- a/go-ssh-keysign/internal/config/model.go
+++ b/go-ssh-keysign/internal/config/model.go
@@ -1,12 +1,14 @@
package config
import (
+ "errors"
"fmt"
"os"
"path/filepath"
"strings"
"binarycodes/ssh-keysign/internal/apperror"
+ "binarycodes/ssh-keysign/internal/service/utilities"
)
type OAuth struct {
@@ -66,14 +68,11 @@ func (c *Config) ValidateHost() error {
return err
}
- return ValidateKeyFile(c.Host.Key)
+ return ValidateKeyFile(c.Host.Key, false)
}
func (c *Config) ValidateUser() error {
var missing []string
- if c.User.Key == "" {
- missing = append(missing, "--key")
- }
if len(c.User.Principals) == 0 {
missing = append(missing, "--principal")
@@ -98,7 +97,11 @@ func (c *Config) ValidateUser() error {
}
}
- return ValidateKeyFile(c.User.Key)
+ if c.User.Key != "" {
+ return ValidateSSHAgent()
+ }
+
+ return ValidateKeyFile(c.User.Key, true)
}
func ValidateDeviceFlow(o OAuth) error {
@@ -149,15 +152,32 @@ func ValidateClientCredential(o OAuth, required bool) (bool, error) {
return len(missing) == 0, nil
}
-func ValidateKeyFile(keyfilePath string) error {
- extension := filepath.Ext(keyfilePath)
+func ValidateKeyFile(keyfilePath string, opt bool) error {
+ if opt && keyfilePath == "" {
+ return nil
+ }
+
+ expandedKeyFilePath, err := utilities.NormalizePath(keyfilePath)
+ if err != nil {
+ return apperror.ErrFileSystem(err)
+ }
+
+ extension := filepath.Ext(expandedKeyFilePath)
if extension != ".pub" {
return apperror.ErrUsage("only public key files are expected here. [Hint: name ending in .pub]")
}
- if _, err := os.Stat(keyfilePath); err != nil {
+ if _, err := os.Stat(expandedKeyFilePath); err != nil {
return apperror.ErrFileSystem(err)
}
return nil
}
+
+func ValidateSSHAgent() error {
+ sock := os.Getenv("SSH_AUTH_SOCK")
+ if sock == "" {
+ return apperror.ErrCert(errors.New("SSH_AUTH_SOCK not set; is ssh-agent running ?"))
+ }
+ return nil
+}
diff --git a/go-ssh-keysign/internal/constants/constants.go b/go-ssh-keysign/internal/constants/constants.go
index c561a7a..a52e91f 100644
--- a/go-ssh-keysign/internal/constants/constants.go
+++ b/go-ssh-keysign/internal/constants/constants.go
@@ -9,9 +9,11 @@ const (
defaultDurationForHostKeyInDays time.Duration = 365
defaultDurationForUserKeyInMinutes time.Duration = 30
- AppName string = "ssh-keysign"
- ConfigFileName string = "config.yml"
- EtcDir string = "/etc"
+ AppName string = "ssh-keysign"
+ ConfigFileName string = "config.yml"
+ EtcDir string = "/etc"
+ UserSSHDir string = "~/.ssh"
+ ConfirmCertBeforeUse bool = false
)
func DefaultDurationForHostKey() uint64 {
diff --git a/go-ssh-keysign/internal/service/cacert/cacertclient.go b/go-ssh-keysign/internal/service/cacert/cacertclient.go
index 2558d44..8420a7f 100644
--- a/go-ssh-keysign/internal/service/cacert/cacertclient.go
+++ b/go-ssh-keysign/internal/service/cacert/cacertclient.go
@@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"net/http"
- "path/filepath"
"binarycodes/ssh-keysign/internal/apperror"
"binarycodes/ssh-keysign/internal/config"
@@ -26,9 +25,8 @@ func (CACertClient) userSignURL(cfg config.OAuth) string {
func (c CACertClient) IssueUserCert(ctx context.Context, u *service.UserCertRequestConfig) (*service.SignedResponse, error) {
signRequest := service.SignRequest{
- Filename: filepath.Base(u.UserConfig.Key),
PublicKey: u.PubKey,
- Hostname: u.UserConfig.Principals[0],
+ Principal: u.UserConfig.Principals[0],
}
postBody := new(bytes.Buffer)
@@ -72,9 +70,8 @@ func (c CACertClient) IssueUserCert(ctx context.Context, u *service.UserCertRequ
func (c CACertClient) IssueHostCert(ctx context.Context, h *service.HostCertRequestConfig) (*service.SignedResponse, error) {
signRequest := service.SignRequest{
- Filename: filepath.Base(h.HostConfig.Key),
PublicKey: h.PubKey,
- Hostname: h.HostConfig.Principals[0],
+ Principal: h.HostConfig.Principals[0],
}
postBody := new(bytes.Buffer)
diff --git a/go-ssh-keysign/internal/service/cacert/cacerthandler.go b/go-ssh-keysign/internal/service/cacert/cacerthandler.go
index ae4a67b..42fef0c 100644
--- a/go-ssh-keysign/internal/service/cacert/cacerthandler.go
+++ b/go-ssh-keysign/internal/service/cacert/cacerthandler.go
@@ -2,39 +2,129 @@ package cacert
import (
"context"
+ "errors"
"fmt"
+ "net"
"os"
- "path/filepath"
+ "time"
+
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/agent"
"binarycodes/ssh-keysign/internal/apperror"
+ "binarycodes/ssh-keysign/internal/constants"
+ "binarycodes/ssh-keysign/internal/ctxkeys"
+ "binarycodes/ssh-keysign/internal/logging"
"binarycodes/ssh-keysign/internal/service"
)
const (
- defaultCertFileMode os.FileMode = 0o400
+ defaultCertFileMode os.FileMode = 0o600
+ defaultPrivateFileMode os.FileMode = 0o600
+ defaultPublicFileMode os.FileMode = 0o600
)
type CACertHandler struct{}
-func (c CACertHandler) StoreUserCert(ctx context.Context, u *service.UserCertHandlerConfig) (path string, err error) {
- return c.writeCertForKey(u.UserConfig.Key, u.SignedResponse)
+func (c CACertHandler) StoreUserCertFile(ctx context.Context, u *service.UserCertHandlerConfig) (agentmode bool, path string, err error) {
+ p := ctxkeys.PrinterFrom(ctx)
+
+ if u.Keys.KeyPair != nil {
+ p.V(logging.Verbose).Println("writing certificate to ssh agent")
+
+ if err := c.StoreUserCertAgent(ctx, u); err != nil {
+ return true, "", err
+ }
+
+ return true, "", nil
+ }
+
+ certSaveFilePath, err := u.Keys.FetchCertFileName()
+ if err != nil {
+ return false, "", err
+ }
+
+ p.V(logging.Verbose).Printf("writing certificate to %s\n", certSaveFilePath)
+
+ path, err = c.writeCertForKey(certSaveFilePath, u.SignedResponse)
+ return false, path, err
}
-func (c CACertHandler) StoreHostCert(ctx context.Context, h *service.HostCertHandlerConfig) (path string, err error) {
- return c.writeCertForKey(h.HostConfig.Key, h.SignedResponse)
+func (c CACertHandler) StoreHostCertFile(ctx context.Context, h *service.HostCertHandlerConfig) (path string, err error) {
+ return c.writeCertForKey(h.CertSaveFilePath, h.SignedResponse)
}
func (CACertHandler) writeCertForKey(keyfilePath string, s service.SignedResponse) (path string, err error) {
- dir := filepath.Dir(keyfilePath)
+ if err := os.WriteFile(keyfilePath, []byte(s.SignedPublicKey), defaultCertFileMode); err != nil {
+ return "", apperror.ErrFileSystem(fmt.Errorf("writing cert file %q: %w", path, err))
+ }
+
+ return keyfilePath, nil
+}
+
+func (CACertHandler) StoreKeyPair(privateFilePath string, k service.ED25519KeyPair) (err error) {
+ publicFilePath := fmt.Sprintf("%s.pub", privateFilePath)
+
+ if err := os.WriteFile(privateFilePath, k.PrivateKeyBytes, defaultPrivateFileMode); err != nil {
+ return apperror.ErrFileSystem(fmt.Errorf("writing cert file %q: %w", privateFilePath, err))
+ }
+
+ if err := os.WriteFile(publicFilePath, []byte(k.PublicKeyString), defaultPublicFileMode); err != nil {
+ return apperror.ErrFileSystem(fmt.Errorf("writing cert file %q: %w", publicFilePath, err))
+ }
+
+ return nil
+}
- path, err = filepath.Abs(filepath.Join(dir, s.Filename))
+func (CACertHandler) StoreUserCertAgent(ctx context.Context, u *service.UserCertHandlerConfig) error {
+ log := ctxkeys.LoggerFrom(ctx)
+
+ sock := os.Getenv("SSH_AUTH_SOCK")
+ if sock == "" {
+ return errors.New("SSH_AUTH_SOCK not set; is ssh-agent running?")
+ }
+
+ conn, err := net.Dial("unix", sock)
if err != nil {
- return "", apperror.ErrFileSystem(fmt.Errorf("resolving absolute path: %w", err))
+ return fmt.Errorf("connect to ssh-agent: %w", err)
}
- if err := os.WriteFile(path, []byte(s.SignedPublicKey), defaultCertFileMode); err != nil {
- return "", apperror.ErrFileSystem(fmt.Errorf("writing cert file %q: %w", path, err))
+ defer func() {
+ if err := conn.Close(); err != nil {
+ log.Error(err.Error())
+ }
+ }()
+
+ pub, _, _, _, err := ssh.ParseAuthorizedKey([]byte(u.SignedResponse.SignedPublicKey))
+ if err != nil {
+ return apperror.ErrCert(fmt.Errorf("parse user certificate: %w", err))
+ }
+
+ cert, ok := pub.(*ssh.Certificate)
+ if !ok {
+ return errors.New("provided blob is not an ssh Certificate")
+ }
+
+ lifetime := constants.DefaultDurationForUserKey()
+
+ now := uint64(time.Now().Unix())
+ if cert.ValidBefore != ssh.CertTimeInfinity && cert.ValidBefore > now {
+ certDuration := uint64(time.Duration(cert.ValidBefore-now) * time.Second)
+ lifetime = min(lifetime, certDuration)
+ }
+
+ add := agent.AddedKey{
+ PrivateKey: u.Keys.KeyPair.PrivateKey,
+ Certificate: cert,
+ Comment: time.Now().String(),
+ LifetimeSecs: uint32(lifetime),
+ ConfirmBeforeUse: constants.ConfirmCertBeforeUse,
+ }
+
+ ag := agent.NewClient(conn)
+ if err := ag.Add(add); err != nil {
+ return fmt.Errorf("add key+cert to ssh-agent: %w", err)
}
- return path, nil
+ return nil
}
diff --git a/go-ssh-keysign/internal/service/hostsvc/service.go b/go-ssh-keysign/internal/service/hostsvc/service.go
index d25192a..31d74a7 100644
--- a/go-ssh-keysign/internal/service/hostsvc/service.go
+++ b/go-ssh-keysign/internal/service/hostsvc/service.go
@@ -38,6 +38,11 @@ func (HostService) SignHostKey(ctx context.Context, r *service.Runner) error {
return apperror.ErrFileSystem(err)
}
+ keys := &service.Keys{
+ Filename: cfg.Host.Key,
+ PublicKey: key,
+ }
+
p.V(logging.VeryVerbose).Printf("found key type: %v | public key: %v\n", kType, key)
log.Info("public key details",
zap.String("type", kType),
@@ -70,15 +75,18 @@ func (HostService) SignHostKey(ctx context.Context, r *service.Runner) error {
}
p.V(logging.VeryVerbose).Println("received signed certificate")
- log.Info("signed certificate received",
- zap.String("filename", signedResponse.Filename),
- )
+ log.Info("signed certificate received")
p.V(logging.VeryVerbose).Println("storing the certificate")
- path, err := r.CertHandler.StoreHostCert(ctx, &service.HostCertHandlerConfig{
- HostConfig: cfg.Host,
- SignedResponse: *signedResponse,
+ certSaveFilePath, err := keys.FetchCertFileName()
+ if err != nil {
+ return err
+ }
+
+ path, err := r.CertHandler.StoreHostCertFile(ctx, &service.HostCertHandlerConfig{
+ CertSaveFilePath: certSaveFilePath,
+ SignedResponse: *signedResponse,
})
if err != nil {
return err
diff --git a/go-ssh-keysign/internal/service/keys/keyshandler.go b/go-ssh-keysign/internal/service/keys/keyshandler.go
index 401c065..20d0e37 100644
--- a/go-ssh-keysign/internal/service/keys/keyshandler.go
+++ b/go-ssh-keysign/internal/service/keys/keyshandler.go
@@ -2,37 +2,22 @@ package keys
import (
"context"
+ "crypto/ed25519"
+ "crypto/rand"
+ "encoding/pem"
"os"
- "path/filepath"
"strings"
"golang.org/x/crypto/ssh"
+
+ "binarycodes/ssh-keysign/internal/service"
+ "binarycodes/ssh-keysign/internal/service/utilities"
)
type CAKeyHandler struct{}
-func expandPath(p string) (string, error) {
- path := p
-
- home, err := os.UserHomeDir()
- if err != nil {
- return "", err
- }
-
- if trimmed, found := strings.CutPrefix(p, "~"); found {
- return filepath.Join(home, trimmed), nil
- }
-
- if trimmed, found := strings.CutPrefix(p, "$HOME"); found {
- return filepath.Join(home, trimmed), nil
- }
-
- /* allow expansion of other environment variables */
- return os.ExpandEnv(path), nil
-}
-
-func (CAKeyHandler) ReadPublicKey(ctx context.Context, path string) (keyType, pubKey string, err error) {
- p, err := expandPath(path)
+func (c CAKeyHandler) ReadPublicKey(ctx context.Context, path string) (keyType, pubKey string, err error) {
+ p, err := utilities.NormalizePath(path)
if err != nil {
return "", "", err
}
@@ -41,10 +26,10 @@ func (CAKeyHandler) ReadPublicKey(ctx context.Context, path string) (keyType, pu
if err != nil {
return "", "", err
}
- return parsePublicKey(b)
+ return c.parsePublicKey(b)
}
-func parsePublicKey(pub []byte) (keyType, pubKey string, err error) {
+func (CAKeyHandler) parsePublicKey(pub []byte) (keyType, pubKey string, err error) {
pk, _, _, _, err := ssh.ParseAuthorizedKey(pub)
if err != nil {
return "", "", err
@@ -54,5 +39,34 @@ func parsePublicKey(pub []byte) (keyType, pubKey string, err error) {
return pk.Type(), pubKeyStr, nil
}
-func (CAKeyHandler) WriteAtomic(path string, data []byte, perm uint32) error { return nil }
-func (CAKeyHandler) BackupIfExists(path string) error { return nil }
+func (c CAKeyHandler) NewEd25519(ctx context.Context) (*service.ED25519KeyPair, error) {
+ pub, priv, err := ed25519.GenerateKey(rand.Reader)
+ if err != nil {
+ return nil, err
+ }
+
+ sshPubKey, err := ssh.NewPublicKey(pub)
+ if err != nil {
+ return nil, err
+ }
+
+ pubKeyMarshalled := string(ssh.MarshalAuthorizedKey(sshPubKey))
+ kType, pubKeyString, err := c.parsePublicKey([]byte(pubKeyMarshalled))
+ if err != nil {
+ return nil, err
+ }
+
+ privBlock, err := ssh.MarshalPrivateKey(priv, "")
+ if err != nil {
+ return nil, err
+ }
+
+ privateKeyBytes := pem.EncodeToMemory(privBlock)
+
+ return &service.ED25519KeyPair{
+ PrivateKey: &priv,
+ PrivateKeyBytes: privateKeyBytes,
+ PublicKeyString: pubKeyString,
+ Type: kType,
+ }, err
+}
diff --git a/go-ssh-keysign/internal/service/types.go b/go-ssh-keysign/internal/service/types.go
index 5ba9e49..a0764f5 100644
--- a/go-ssh-keysign/internal/service/types.go
+++ b/go-ssh-keysign/internal/service/types.go
@@ -2,16 +2,17 @@ package service
import (
"context"
+ "crypto/ed25519"
"binarycodes/ssh-keysign/internal/config"
"binarycodes/ssh-keysign/internal/ctxkeys"
"binarycodes/ssh-keysign/internal/logging"
+ "binarycodes/ssh-keysign/internal/service/utilities"
)
type KeyHandler interface {
ReadPublicKey(ctx context.Context, path string) (keyType, pubKey string, err error)
- WriteAtomic(path string, data []byte, perm uint32) error
- BackupIfExists(path string) error
+ NewEd25519(ctx context.Context) (*ED25519KeyPair, error)
}
type CertClient interface {
@@ -25,8 +26,9 @@ type OAuthClient interface {
}
type CertHandler interface {
- StoreUserCert(ctx context.Context, u *UserCertHandlerConfig) (path string, err error)
- StoreHostCert(ctx context.Context, h *HostCertHandlerConfig) (path string, err error)
+ StoreUserCertFile(ctx context.Context, u *UserCertHandlerConfig) (agent bool, path string, err error)
+ StoreUserCertAgent(ctx context.Context, u *UserCertHandlerConfig) error
+ StoreHostCertFile(ctx context.Context, h *HostCertHandlerConfig) (path string, err error)
}
type UserCertRequestConfig struct {
@@ -44,13 +46,13 @@ type HostCertRequestConfig struct {
}
type UserCertHandlerConfig struct {
- UserConfig config.User
+ Keys Keys
SignedResponse SignedResponse
}
type HostCertHandlerConfig struct {
- HostConfig config.Host
- SignedResponse SignedResponse
+ CertSaveFilePath string
+ SignedResponse SignedResponse
}
type Runner struct {
@@ -86,13 +88,11 @@ func (a AccessToken) OK(ctx context.Context) bool {
}
type SignRequest struct {
- Filename string `json:"filename"`
PublicKey string `json:"publicKey"`
- Hostname string `json:"principal"`
+ Principal string `json:"principal"`
}
type SignedResponse struct {
- Filename string `json:"filename"`
SignedPublicKey string `json:"signedKey"`
}
@@ -104,3 +104,32 @@ type DeviceFlowStartResponse struct {
ExpiresIn uint64 `json:"expires_in"`
Interval uint64 `json:"interval"`
}
+
+type ED25519KeyPair struct {
+ PrivateKey *ed25519.PrivateKey
+ PrivateKeyBytes []byte
+ PublicKeyString string
+ Type string
+}
+
+type Keys struct {
+ Filename string
+ PublicKey string
+ KeyPair *ED25519KeyPair
+}
+
+func (k Keys) FetchPublicKey() string {
+ if k.Filename != "" {
+ return k.PublicKey
+ }
+
+ return k.KeyPair.PublicKeyString
+}
+
+func (k Keys) FetchCertFileName() (string, error) {
+ if k.Filename != "" {
+ return utilities.GetCertificateFilePath(k.Filename)
+ }
+
+ return "", nil
+}
diff --git a/go-ssh-keysign/internal/service/usersvc/service.go b/go-ssh-keysign/internal/service/usersvc/service.go
index dadb5e4..95fd15a 100644
--- a/go-ssh-keysign/internal/service/usersvc/service.go
+++ b/go-ssh-keysign/internal/service/usersvc/service.go
@@ -18,11 +18,11 @@ type Service interface {
SignUserKey(ctx context.Context, r *service.Runner) error
}
-func (UserService) SignUserKey(ctx context.Context, r *service.Runner) error {
+func (u UserService) SignUserKey(ctx context.Context, r *service.Runner) error {
log := ctxkeys.LoggerFrom(ctx)
p := ctxkeys.PrinterFrom(ctx)
-
cfg := r.Config
+
log.Info("user run",
zap.String("key", cfg.User.Key),
zap.Strings("principal", cfg.User.Principals),
@@ -32,19 +32,75 @@ func (UserService) SignUserKey(ctx context.Context, r *service.Runner) error {
zap.String("token-url", cfg.OAuth.TokenURL),
)
- p.V(logging.Verbose).Println("fetching key details")
+ keys, err := u.fetchKeys(ctx, r)
+ if err != nil {
+ return err
+ }
+
+ accessToken, err := u.fetchAccessToken(ctx, r)
+ if err != nil {
+ return err
+ }
- kType, key, err := r.KeyHandler.ReadPublicKey(ctx, cfg.User.Key)
+ signedResponse, err := u.certSignRequest(ctx, r, keys, accessToken)
if err != nil {
return err
}
- p.V(logging.VeryVerbose).Printf("found key type: %v | public key: %v\n", kType, key)
+ if err := u.storeCertificate(ctx, r, keys, signedResponse); err != nil {
+ return err
+ }
+
+ p.V(logging.VeryVerbose).Println("done")
+ return nil
+}
+
+func (UserService) fetchKeys(ctx context.Context, r *service.Runner) (*service.Keys, error) {
+ log := ctxkeys.LoggerFrom(ctx)
+ p := ctxkeys.PrinterFrom(ctx)
+ cfg := r.Config
+
+ p.V(logging.Verbose).Println("fetching key details")
+
+ if cfg.User.Key != "" {
+ kType, key, err := r.KeyHandler.ReadPublicKey(ctx, cfg.User.Key)
+ if err != nil {
+ return nil, err
+ }
+
+ p.V(logging.VeryVerbose).Printf("found key type: %v | public key: %v\n", kType, key)
+ log.Info("public key details",
+ zap.String("type", kType),
+ zap.String("key", key),
+ )
+
+ return &service.Keys{
+ Filename: cfg.User.Key,
+ PublicKey: key,
+ }, nil
+ }
+
+ keyPair, err := r.KeyHandler.NewEd25519(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ p.V(logging.VeryVerbose).Printf("created key type: %v | public key: %v\n", keyPair.Type, keyPair.PublicKeyString)
log.Info("public key details",
- zap.String("type", kType),
- zap.String("key", key),
+ zap.String("type", keyPair.Type),
+ zap.String("key", keyPair.PublicKeyString),
)
+ return &service.Keys{
+ KeyPair: keyPair,
+ }, nil
+}
+
+func (UserService) fetchAccessToken(ctx context.Context, r *service.Runner) (token *service.AccessToken, err error) {
+ log := ctxkeys.LoggerFrom(ctx)
+ p := ctxkeys.PrinterFrom(ctx)
+ cfg := r.Config
+
p.V(logging.Verbose).Println("initiating connection to OAuth")
var accessToken *service.AccessToken
@@ -53,18 +109,18 @@ func (UserService) SignUserKey(ctx context.Context, r *service.Runner) error {
p.V(logging.Verbose).Println("using client credential")
accessToken, err = r.OAuthClient.ClientCredentialLogin(ctx, cfg.OAuth)
if err != nil {
- return apperror.ErrAuth(err)
+ return nil, apperror.ErrAuth(err)
}
} else {
p.V(logging.Verbose).Println("using device flow")
accessToken, err = r.OAuthClient.DeviceFlowLogin(ctx, cfg.OAuth)
if err != nil {
- return apperror.ErrAuth(err)
+ return nil, apperror.ErrAuth(err)
}
}
if accessToken == nil || !accessToken.OK(ctx) {
- return apperror.ErrAuth(errors.New("failed to retrieve access token"))
+ return nil, apperror.ErrAuth(errors.New("failed to retrieve access token"))
}
p.V(logging.VeryVerbose).Println("received access token")
@@ -73,38 +129,56 @@ func (UserService) SignUserKey(ctx context.Context, r *service.Runner) error {
zap.Uint64("expires_in", accessToken.ExpiresIn),
)
+ return accessToken, nil
+}
+
+func (UserService) certSignRequest(ctx context.Context, r *service.Runner, k *service.Keys, token *service.AccessToken) (certResp *service.SignedResponse, err error) {
+ log := ctxkeys.LoggerFrom(ctx)
+ p := ctxkeys.PrinterFrom(ctx)
+ cfg := r.Config
+
p.V(logging.Verbose).Println("initiating connection to CA server to sign public key")
signedResponse, err := r.CertClient.IssueUserCert(ctx, &service.UserCertRequestConfig{
- UserConfig: r.Config.User,
- OAuthConfig: r.Config.OAuth,
- PubKey: key,
- Token: *accessToken,
+ UserConfig: cfg.User,
+ OAuthConfig: cfg.OAuth,
+ PubKey: k.FetchPublicKey(),
+ Token: *token,
})
if err != nil {
- return apperror.ErrNet(err)
+ return nil, apperror.ErrNet(err)
}
p.V(logging.VeryVerbose).Println("received signed certificate")
- log.Info("signed certificate received",
- zap.String("filename", signedResponse.Filename),
- )
+ log.Info("signed certificate received")
+
+ return signedResponse, nil
+}
+
+func (UserService) storeCertificate(ctx context.Context, r *service.Runner, k *service.Keys, s *service.SignedResponse) (err error) {
+ log := ctxkeys.LoggerFrom(ctx)
+ p := ctxkeys.PrinterFrom(ctx)
p.V(logging.VeryVerbose).Println("storing the certificate")
- path, err := r.CertHandler.StoreUserCert(ctx, &service.UserCertHandlerConfig{
- UserConfig: cfg.User,
- SignedResponse: *signedResponse,
+ agent, path, err := r.CertHandler.StoreUserCertFile(ctx, &service.UserCertHandlerConfig{
+ Keys: *k,
+ SignedResponse: *s,
})
if err != nil {
return err
}
- p.V(logging.Normal).Printf("certificate stored at %s", path)
+ if agent {
+ p.V(logging.Normal).Println("certificate stored in ssh-agent")
+ } else {
+ p.V(logging.Normal).Printf("certificate stored at %s\n", path)
+ }
+
log.Info("certificate stored",
+ zap.Bool("agent", agent),
zap.String("filename", path),
)
- p.V(logging.VeryVerbose).Println("done")
return nil
}
diff --git a/go-ssh-keysign/internal/service/utilities/util.go b/go-ssh-keysign/internal/service/utilities/util.go
new file mode 100644
index 0000000..30f0af2
--- /dev/null
+++ b/go-ssh-keysign/internal/service/utilities/util.go
@@ -0,0 +1,52 @@
+package utilities
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "binarycodes/ssh-keysign/internal/apperror"
+)
+
+func NormalizePath(p string) (string, error) {
+ path := p
+
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return "", err
+ }
+
+ if trimmed, found := strings.CutPrefix(p, "~"); found {
+ return filepath.Join(home, trimmed), nil
+ }
+
+ if trimmed, found := strings.CutPrefix(p, "$HOME"); found {
+ return filepath.Join(home, trimmed), nil
+ }
+
+ /* allow expansion of other environment variables */
+ return os.ExpandEnv(path), nil
+}
+
+// The parameter is expected to be an abolute file path
+func GetCertificateFilePath(fp string) (string, error) {
+ normalized, err := NormalizePath(fp)
+ if err != nil {
+ return "", apperror.ErrFileSystem(err)
+ }
+
+ dir := filepath.Dir(normalized)
+ basename := filepath.Base(normalized)
+ extension := filepath.Ext(normalized)
+
+ nameWithoutExtension := strings.TrimSuffix(basename, extension)
+
+ certFileName := fmt.Sprintf("%s-cert%s", nameWithoutExtension, extension)
+ path, err := filepath.Abs(filepath.Join(dir, certFileName))
+ if err != nil {
+ return "", apperror.ErrFileSystem(err)
+ }
+
+ return path, nil
+}
diff --git a/go-ssh-keysign/user_config.sample.yml b/go-ssh-keysign/user_config.sample.yml
index bff939f..7a6767a 100644
--- a/go-ssh-keysign/user_config.sample.yml
+++ b/go-ssh-keysign/user_config.sample.yml
@@ -8,10 +8,9 @@ device-flow-url: "http://10.88.0.100:8090/realms/my-test-realm/protocol/openid-c
token-poll-url: "http://10.88.0.100:8090/realms/my-test-realm/protocol/openid-connect/token"
user:
- key: "testdata/id.pub"
+ #key: "testdata/id.pub"
principal:
- - alice
- - devops
+ - binarycodes
duration: 3600
host:
diff --git a/server/pom.xml b/server/pom.xml
index 8b12e64..41553d4 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -7,7 +7,7 @@
io.binarycodes.homelab
ssh-signer-mono
SSH KeySigner
- 0.0.5
+ 0.0.6
pom
diff --git a/server/ssh-key-signer-server/pom.xml b/server/ssh-key-signer-server/pom.xml
index 90482c1..7c356d6 100644
--- a/server/ssh-key-signer-server/pom.xml
+++ b/server/ssh-key-signer-server/pom.xml
@@ -5,7 +5,7 @@
io.binarycodes.homelab
ssh-key-signer-server
- 0.0.5
+ 0.0.6
jar
Server - SSH Key Signer
@@ -14,7 +14,7 @@
21
24.9.2
- 0.0.5
+ 0.0.6
diff --git a/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/KeyController.java b/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/KeyController.java
index 3c159ea..5db38bd 100644
--- a/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/KeyController.java
+++ b/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/KeyController.java
@@ -38,7 +38,6 @@ public ResponseEntity signUserKey(final JwtAuthenticati
}
final var signed = keyService.signUserKey(
- signPublicKeyRequest.filename(),
signPublicKeyRequest.publicKey(),
principal.getToken().getId(),
signPublicKeyRequest.principal()
@@ -58,7 +57,6 @@ public ResponseEntity signHostKey(final JwtAuthenticati
}
final var signed = keyService.signHostKey(
- signPublicKeyRequest.filename(),
signPublicKeyRequest.publicKey(),
principal.getToken().getId(),
signPublicKeyRequest.principal()
diff --git a/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/KeyService.java b/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/KeyService.java
index dc041b5..315775e 100644
--- a/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/KeyService.java
+++ b/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/KeyService.java
@@ -11,7 +11,6 @@
import io.binarycodes.homelab.lib.SignedPublicKeyDownload;
import io.binarycodes.homelab.sshkeysigner.config.ApplicationProperties;
import lombok.extern.log4j.Log4j2;
-import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Service;
import java.io.IOException;
@@ -26,7 +25,6 @@
public class KeyService {
/* https://jadaptive.com/app/manpage/en/article/2895616 */
- private static final String CERTIFICATE_FILE_NAME_SUFFIX = "cert";
private final ApplicationProperties applicationProperties;
public KeyService(final ApplicationProperties applicationProperties) {
@@ -113,48 +111,48 @@ private SshKeyPair keyInfoToKeyPair(final KeyInfo keyInfo, final String passphra
/**
* Signs the given key for user
*/
- public Optional signUserKey(final String filename, final String pubKey, final String keyId, final String principal) {
- return signUserKey(filename, pubKey, keyId, List.of(principal));
+ public Optional signUserKey(final String pubKey, final String keyId, final String principal) {
+ return signUserKey(pubKey, keyId, List.of(principal));
}
/**
* Signs the given key for user
*/
- public Optional signUserKey(final String filename, final String pubKey, final String keyId, final List principals) {
+ public Optional signUserKey(final String pubKey, final String keyId, final List principals) {
final var bytes = pubKey.getBytes(StandardCharsets.UTF_8);
- return signUserKey(filename, bytes, keyId, principals);
+ return signUserKey(bytes, keyId, principals);
}
/**
* Signs the given key for user
*/
- public Optional signUserKey(final String filename, final byte[] bytes, final String keyId, final List principals) {
- return signKey(SshCertificateType.USER, filename, bytes, keyId, principals, applicationProperties.caUserValidity());
+ public Optional signUserKey(final byte[] bytes, final String keyId, final List principals) {
+ return signKey(SshCertificateType.USER, bytes, keyId, principals, applicationProperties.caUserValidity());
}
/**
* Signs the given key for host
*/
- public Optional signHostKey(final String filename, final String pubKey, final String keyId, final String principal) {
- return signHostKey(filename, pubKey, keyId, List.of(principal));
+ public Optional signHostKey(final String pubKey, final String keyId, final String principal) {
+ return signHostKey(pubKey, keyId, List.of(principal));
}
/**
* Signs the given key for host
*/
- public Optional signHostKey(final String filename, final String pubKey, final String keyId, final List principals) {
+ public Optional signHostKey(final String pubKey, final String keyId, final List principals) {
final var bytes = pubKey.getBytes(StandardCharsets.UTF_8);
- return signHostKey(filename, bytes, keyId, principals);
+ return signHostKey(bytes, keyId, principals);
}
/**
* Signs the given key for host
*/
- public Optional signHostKey(final String filename, final byte[] bytes, final String keyId, final List principals) {
- return signKey(SshCertificateType.HOST, filename, bytes, keyId, principals, applicationProperties.caHostValidity());
+ public Optional signHostKey(final byte[] bytes, final String keyId, final List principals) {
+ return signKey(SshCertificateType.HOST, bytes, keyId, principals, applicationProperties.caHostValidity());
}
- private Optional signKey(final SshCertificateType certType, final String filename, final byte[] bytes, final String keyId, final List principals, final Duration validitySeconds) {
+ private Optional signKey(final SshCertificateType certType, final byte[] bytes, final String keyId, final List principals, final Duration validitySeconds) {
try {
final var publicKeyFileToSign = SshPublicKeyFileFactory.parse(bytes);
final var keyPairToSign = SshKeyPair.getKeyPair(null, publicKeyFileToSign.toPublicKey());
@@ -168,9 +166,8 @@ private Optional signKey(final SshCertificateType certT
final var signedKey = SshPublicKeyFileFactory.create(signed.getCertificate(), publicKeyFileToSign.getComment(), SshPublicKeyFileFactory.OPENSSH_FORMAT);
final var signedKeyString = new String(signedKey.getFormattedKey(), StandardCharsets.UTF_8);
- final var downloadFilename = "%s-%s.%s".formatted(FilenameUtils.getBaseName(filename), CERTIFICATE_FILE_NAME_SUFFIX, FilenameUtils.getExtension(filename));
- return Optional.of(new SignedPublicKeyDownload(downloadFilename, signedKeyString));
+ return Optional.of(new SignedPublicKeyDownload(signedKeyString));
} catch (final IOException e) {
log.error(e.getMessage(), e);
} catch (final InvalidPassphraseException | SshException e) {
diff --git a/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/SshCertManager.java b/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/SshCertManager.java
index 4f4d8b6..9f5d063 100644
--- a/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/SshCertManager.java
+++ b/server/ssh-key-signer-server/src/main/java/io/binarycodes/homelab/sshkeysigner/keymanagement/SshCertManager.java
@@ -66,7 +66,9 @@ private static List buildCertificateExtensions(SshCertific
}
final var extensionsBuilder = new CertificateExtension.Builder();
- knownExtensions.forEach(extension -> extensionsBuilder.knownExtension(new NamedCertificateExtension(extension, true)));
+ knownExtensions.stream()
+ .sorted()
+ .forEach(extension -> extensionsBuilder.knownExtension(new NamedCertificateExtension(extension, true)));
return extensionsBuilder.build();
}
diff --git a/server/ssh-signer-common-lib/pom.xml b/server/ssh-signer-common-lib/pom.xml
index 11c292f..6605598 100644
--- a/server/ssh-signer-common-lib/pom.xml
+++ b/server/ssh-signer-common-lib/pom.xml
@@ -6,7 +6,7 @@
io.binarycodes.homelab
ssh-signer-common-lib
- 0.0.5
+ 0.0.6
Common Library - SSH Key Signer
diff --git a/server/ssh-signer-common-lib/src/main/java/io/binarycodes/homelab/lib/SignPublicKeyRequest.java b/server/ssh-signer-common-lib/src/main/java/io/binarycodes/homelab/lib/SignPublicKeyRequest.java
index 2dc44ec..0df4263 100644
--- a/server/ssh-signer-common-lib/src/main/java/io/binarycodes/homelab/lib/SignPublicKeyRequest.java
+++ b/server/ssh-signer-common-lib/src/main/java/io/binarycodes/homelab/lib/SignPublicKeyRequest.java
@@ -1,4 +1,4 @@
package io.binarycodes.homelab.lib;
-public record SignPublicKeyRequest(String filename, String publicKey, String principal) {
+public record SignPublicKeyRequest(String publicKey, String principal) {
}
diff --git a/server/ssh-signer-common-lib/src/main/java/io/binarycodes/homelab/lib/SignedPublicKeyDownload.java b/server/ssh-signer-common-lib/src/main/java/io/binarycodes/homelab/lib/SignedPublicKeyDownload.java
index 15e3045..5235073 100644
--- a/server/ssh-signer-common-lib/src/main/java/io/binarycodes/homelab/lib/SignedPublicKeyDownload.java
+++ b/server/ssh-signer-common-lib/src/main/java/io/binarycodes/homelab/lib/SignedPublicKeyDownload.java
@@ -1,4 +1,4 @@
package io.binarycodes.homelab.lib;
-public record SignedPublicKeyDownload(String filename, String signedKey) {
+public record SignedPublicKeyDownload(String signedKey) {
}