Compare commits

...

3 Commits

Author SHA1 Message Date
Markus Pesch e7d5a6b92b
fix: file permissions 2021-04-11 13:37:32 +02:00
Markus Pesch 2ef390bd8f
fix: add test functions
changes:
- fix: test add, remove, read and write functions
- fix: parsing ssh key aliases
- fix: skip invalid ssh keys
2020-09-03 10:25:25 +02:00
Markus Pesch a50da9bbed
fix: remove ssh keys 2020-09-02 13:28:21 +02:00
7 changed files with 301 additions and 39 deletions

View File

@ -13,13 +13,12 @@ steps:
- pull_request
- tag
# steps:
# - name: test-unit
# image: docker.io/volkerraschek/build-image:latest
# commands:
# - make test/unit
# when:
# event:
# - push
# - pull_request
# - tag
- name: test-unit
image: docker.io/volkerraschek/build-image:latest
commands:
- make test/unit
when:
event:
- push
- pull_request
- tag

3
.gitignore vendored
View File

@ -1 +1,2 @@
bin
bin
coverage.txt

View File

@ -64,6 +64,15 @@ PHONY+=clean
clean:
-rm --force --recursive bin/
# TEST
# ==============================================================================
PHONY+=test/unit
test/unit:
go test -v -race -coverprofile=coverage.txt -covermode=atomic -timeout 600s -count=1 ./...
test/coverage: test/unit
go tool cover -html=coverage.txt
# PHONY
# ==============================================================================
# Declare the contents of the PHONY variable as phony. We keep that information

1
go.mod
View File

@ -5,4 +5,5 @@ go 1.15
require (
git.cryptic.systems/volker.raschek/go-logger v0.1.0
github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.6.1
)

4
go.sum
View File

@ -17,6 +17,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
@ -59,6 +60,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
@ -87,6 +89,7 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
@ -130,5 +133,6 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

91
main.go
View File

@ -4,24 +4,22 @@ import (
"bufio"
"errors"
"fmt"
"io"
"log"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"git.cryptic.systems/volker.raschek/go-logger"
"github.com/spf13/cobra"
)
var (
flogger logger.Logger
version string
)
func main() {
flogger = logger.NewLogger(logger.LogLevelDebug)
rootCmd := cobra.Command{
Use: "set-sshkeys",
RunE: rootCmd,
@ -37,8 +35,13 @@ func main() {
func addSSHKeys(sshKeys []*sshKey, newSSHKeys []*sshKey) []*sshKey {
Label:
for i := range newSSHKeys {
if err := newSSHKeys[i].Validate(); err != nil {
continue Label
}
for j := range sshKeys {
if sshKeys[j].Compare(newSSHKeys[i]) {
sshKeys[j].SetAlias(newSSHKeys[i].alias)
continue Label
}
}
@ -49,7 +52,7 @@ Label:
func createAutorizationFile(authorizedKeyFile string) error {
err := os.MkdirAll(filepath.Dir(authorizedKeyFile), 700)
err := os.MkdirAll(filepath.Dir(authorizedKeyFile), 0700)
if err != nil {
return err
}
@ -62,16 +65,21 @@ func createAutorizationFile(authorizedKeyFile string) error {
return f.Close()
}
func readSSHKeys(authorizedKeyFile string) ([]*sshKey, error) {
func readSSHKeysFile(authorizedKeyFile string) ([]*sshKey, error) {
f, err := os.Open(authorizedKeyFile)
if err != nil {
return nil, err
}
defer f.Close()
return readSSHKeys(f)
}
sshKeys := make([]*sshKey, 0)
func readSSHKeys(r io.Reader) ([]*sshKey, error) {
s := bufio.NewScanner(f)
var (
sshKeys = make([]*sshKey, 0)
s = bufio.NewScanner(r)
)
Loop:
for s.Scan() {
@ -107,11 +115,11 @@ Loop:
if err != nil {
return nil, err
}
sshKey.SetAlias(alias)
for i := range sshKeys {
if sshKeys[i].Compare(sshKey) {
sshKeys[i].SetAlias(sshKey.alias)
continue Loop
}
}
@ -119,7 +127,7 @@ Loop:
sshKeys = append(sshKeys, sshKey)
default:
log.Printf("Require two and optional three parts for each line. Get %v parts", len(parts))
log.Printf("WARN: Require two and optional three parts for each line. Get %v parts - SKIP entry", len(parts))
}
}
@ -127,14 +135,20 @@ Loop:
}
func removeSSHKeys(sshKeys []*sshKey, removeSSHKeys []*sshKey) []*sshKey {
for i := range removeSSHKeys {
for j := range sshKeys {
if sshKeys[j].Compare(removeSSHKeys[i]) {
sshKeys = append(sshKeys[:j], sshKeys[j+1:]...)
s := make([]*sshKey, 0)
Loop:
for i := range sshKeys {
for j := range removeSSHKeys {
if sshKeys[i].Compare(removeSSHKeys[j]) {
continue Loop
}
}
s = append(s, sshKeys[i])
}
return sshKeys
return s
}
func rootCmd(cmd *cobra.Command, args []string) error {
@ -171,7 +185,6 @@ func rootCmd(cmd *cobra.Command, args []string) error {
case err == nil:
break
case errors.Is(err, os.ErrNotExist):
flogger.Debug("Create authorization file %v", userAuthorizedKeyFile)
if err := createAutorizationFile(userAuthorizedKeyFile); err != nil {
return err
}
@ -179,12 +192,12 @@ func rootCmd(cmd *cobra.Command, args []string) error {
return err
}
etcAuthorizedKeys, err := readSSHKeys(etcAuthorizedKeyFile)
etcAuthorizedKeys, err := readSSHKeysFile(etcAuthorizedKeyFile)
if err != nil {
return err
}
userAuthorizedKeys, err := readSSHKeys(userAuthorizedKeyFile)
userAuthorizedKeys, err := readSSHKeysFile(userAuthorizedKeyFile)
if err != nil {
return err
}
@ -195,11 +208,10 @@ func rootCmd(cmd *cobra.Command, args []string) error {
userAuthorizedKeys = addSSHKeys(userAuthorizedKeys, etcAuthorizedKeys)
}
return writeSSHKeys(userAuthorizedKeyFile, userAuthorizedKeys)
return writeSSHKeysFile(user, userAuthorizedKeyFile, userAuthorizedKeys)
}
func writeSSHKeys(authorizedKeyFile string, sshKeys []*sshKey) error {
func writeSSHKeysFile(u *user.User, authorizedKeyFile string, sshKeys []*sshKey) error {
if err := createAutorizationFile(authorizedKeyFile); err != nil {
return err
}
@ -208,15 +220,30 @@ func writeSSHKeys(authorizedKeyFile string, sshKeys []*sshKey) error {
if err != nil {
return err
}
defer f.Close()
for i := range sshKeys {
if len(sshKeys[i].alias) > 0 {
fmt.Fprintf(f, "%v %v %v\n", sshKeys[i].algorithm, sshKeys[i].pubKey, sshKeys[i].alias)
} else {
fmt.Fprintf(f, "%v %v\n", sshKeys[i].algorithm, sshKeys[i].pubKey)
}
err = writeSSHKeys(f, sshKeys)
if err != nil {
return err
}
uid, err := strconv.Atoi(u.Uid)
if err != nil {
return err
}
gid, err := strconv.Atoi(u.Gid)
if err != nil {
return err
}
return os.Chown(authorizedKeyFile, uid, gid)
}
func writeSSHKeys(w io.Writer, sshKeys []*sshKey) error {
for i := range sshKeys {
fmt.Fprintln(w, sshKeys[i].String())
}
return nil
}
@ -240,6 +267,14 @@ func (s *sshKey) SetAlias(alias string) {
}
}
func (s *sshKey) String() string {
l := fmt.Sprintf("%v %v", s.algorithm, s.pubKey)
if len(s.alias) > 0 {
l = fmt.Sprintf("%v %v", l, s.alias)
}
return l
}
func (s *sshKey) Validate() error {
entries := map[string]string{
"algorithm": s.algorithm,

213
main_test.go Normal file
View File

@ -0,0 +1,213 @@
package main
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestAdd(t *testing.T) {
require := require.New(t)
sshKeys := addSSHKeys([]*sshKey{}, []*sshKey{{algorithm: "ssh-ed25519", pubKey: "abcdefg"}})
require.Len(sshKeys, 1)
sshKeys = addSSHKeys(sshKeys, []*sshKey{{algorithm: "ssh-ed25519", pubKey: "abcdefg"}})
require.Len(sshKeys, 1)
sshKeys = addSSHKeys(sshKeys, []*sshKey{{algorithm: "ssh-ed25519", pubKey: "abcdefg", alias: "buxdehude"}})
require.Len(sshKeys, 1)
require.Equal("buxdehude", sshKeys[0].alias)
sshKeys = addSSHKeys(sshKeys, []*sshKey{{algorithm: "ssh-ed25519", pubKey: "asdsadasd", alias: "hello@world"}})
require.Len(sshKeys, 2)
sshKeys = addSSHKeys(sshKeys, []*sshKey{{algorithm: "ssh-ed25519", pubKey: "asdsadasd", alias: "world@hello"}})
require.Len(sshKeys, 2)
require.Equal("hello@world", sshKeys[1].alias)
sshKeys = addSSHKeys(sshKeys, []*sshKey{{algorithm: "ssh-ed25519", pubKey: "", alias: "world@hello"}})
require.Len(sshKeys, 2)
sshKeys = addSSHKeys(sshKeys, []*sshKey{{algorithm: "", pubKey: "asdsadasd", alias: "world@hello"}})
require.Len(sshKeys, 2)
}
func TestRead(t *testing.T) {
require := require.New(t)
testCases := []struct {
expectedSSHKeys []*sshKey
b []byte
}{
{
expectedSSHKeys: []*sshKey{
{algorithm: "ssh-ed25519", pubKey: "abcdefg", alias: "hello@world"},
},
b: []byte(`ssh-ed25519 abcdefg hello@world`),
},
{
expectedSSHKeys: []*sshKey{
{algorithm: "ssh-ed25519", pubKey: "abcdefg", alias: "hello@world"},
},
b: []byte(`ssh-ed25519 abcdefg hello@world
ssh-ed25519 abcdefg`),
},
{
expectedSSHKeys: []*sshKey{
{algorithm: "ssh-ed25519", pubKey: "abcdefg", alias: "hello@world"},
},
b: []byte(`ssh-ed25519 abcdefg
ssh-ed25519 abcdefg hello@world`),
},
{
expectedSSHKeys: []*sshKey{
{algorithm: "ssh-ed25519", pubKey: "abcdefg", alias: "hello@world"},
},
b: []byte(`ssh-ed25519 abcdefg hello@world
ssh-ed25519 abcdefg world@hello`),
},
{
expectedSSHKeys: []*sshKey{
{algorithm: "ssh-ed25519", pubKey: "abcdefg", alias: "hello@world"},
},
b: []byte(`ssh-ed25519 abcdefg hello@world
ssh-ed25519
abcdefg test`),
},
}
for i := range testCases {
sshKeys, err := readSSHKeys(bytes.NewReader(testCases[i].b))
require.NoError(err)
require.ElementsMatch(testCases[i].expectedSSHKeys, sshKeys)
}
}
func TestRemove(t *testing.T) {
require := require.New(t)
s := []*sshKey{
{algorithm: "ssh-ed25519", pubKey: "sdfsdf", alias: "hello@world"},
{algorithm: "ssh-rsa", pubKey: "asdfasdadsad", alias: "world@hello"},
}
r := []*sshKey{
{algorithm: "ssh-ed25519", pubKey: "sdfsdf", alias: "hello@world"},
}
result := removeSSHKeys(s, r)
require.Len(result, 1)
r = []*sshKey{
{algorithm: "ssh-ed25519", pubKey: "sdfsdf", alias: "hello@world"},
{algorithm: "ssh-rsa", pubKey: "asdfasdadsad", alias: "world@hello"},
}
result = removeSSHKeys(s, r)
require.Len(result, 0)
r = []*sshKey{
{algorithm: "ssh-ed25519", pubKey: "sdfsdf"},
}
result = removeSSHKeys(s, r)
require.Len(result, 1)
}
func TestSSHKeyCompare(t *testing.T) {
require := require.New(t)
testCases := []struct {
s *sshKey
r *sshKey
expectedValue bool
}{
{&sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh", alias: "world@hello"}, &sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh", alias: "world@hello"}, true},
{&sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh", alias: "world@hello"}, &sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh"}, true},
{&sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh"}, &sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh", alias: "world@hello"}, true},
{&sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh", alias: "world@hello"}, &sshKey{algorithm: "ssh-ed25519", pubKey: "sdfsdf", alias: "hello@world"}, false},
{&sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh", alias: "world@hello"}, &sshKey{algorithm: "ssh-rsa", pubKey: "sdfsdf", alias: "hello@world"}, false},
{&sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh", alias: "world@hello"}, &sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh", alias: "hello@world"}, true},
}
for i := range testCases {
require.Equal(testCases[i].expectedValue, testCases[i].s.Compare(testCases[i].r), "TestCase %v does not match the expected value", i)
}
}
func TestSSHKeyString(t *testing.T) {
require := require.New(t)
s := []*sshKey{
{algorithm: "ssh-ed25519", pubKey: "sdfsdf", alias: "hello@world"},
{algorithm: "ssh-rsa", pubKey: "asdfasdadsad", alias: "world@hello"},
{algorithm: "ssh-rsa", pubKey: "asdfasdsdfsfadsad"},
}
b := []string{
"ssh-ed25519 sdfsdf hello@world",
"ssh-rsa asdfasdadsad world@hello",
"ssh-rsa asdfasdsdfsfadsad",
}
for i := range s {
require.Equal(b[i], s[i].String())
}
}
func TestSSHKeyValidate(t *testing.T) {
require := require.New(t)
testCases := []struct {
s *sshKey
expectedValue error
}{
{&sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh", alias: "world@hello"}, nil},
{&sshKey{algorithm: "ssh-rsa", pubKey: "abcdefgh"}, nil},
{&sshKey{algorithm: "ssh-rsa", pubKey: ""}, fmt.Errorf("Missing attribute pubKey")},
{&sshKey{algorithm: "ssh-rsa"}, fmt.Errorf("Missing attribute pubKey")},
{&sshKey{algorithm: "", pubKey: "abcdefgh"}, fmt.Errorf("Missing attribute algorithm")},
{&sshKey{pubKey: "abcdefgh"}, fmt.Errorf("Missing attribute algorithm")},
{&sshKey{algorithm: "ssh-rsa", alias: "sdfsdf"}, fmt.Errorf("Missing attribute pubKey")},
{&sshKey{pubKey: "abcdefgh", alias: "sdfsdf"}, fmt.Errorf("Missing attribute algorithm")},
}
for i := range testCases {
if testCases[i].expectedValue == nil {
require.NoError(testCases[i].s.Validate())
continue
}
require.EqualError(testCases[i].expectedValue, testCases[i].s.Validate().Error())
}
}
func TestWrite(t *testing.T) {
require := require.New(t)
testCases := []struct {
expectedSSHKeys []*sshKey
b []byte
}{
{
expectedSSHKeys: []*sshKey{
{algorithm: "ssh-ed25519", pubKey: "abcdefg", alias: "hello@world"},
{algorithm: "ssh-rsa", pubKey: "gfedcba"},
{algorithm: "ssh-rsa", pubKey: "1234567", alias: "world@hello"},
},
b: []byte(`ssh-ed25519 abcdefg hello@world
ssh-rsa gfedcba
ssh-rsa 1234567 world@hello
`),
},
}
for i := range testCases {
b := bytes.NewBuffer([]byte{})
err := writeSSHKeys(b, testCases[i].expectedSSHKeys)
require.NoError(err)
require.Equal(testCases[i].b, b.Bytes())
}
}