Skip to content

Commit

Permalink
Merge pull request #7 from alexandreh2ag/update_storage_owner
Browse files Browse the repository at this point in the history
Add owner & group on storages certificates
  • Loading branch information
alexandreh2ag authored Jan 19, 2025
2 parents ae133e6 + e67b151 commit 7d73a70
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 13 deletions.
9 changes: 7 additions & 2 deletions apps/agent/storage/certificate/certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import (
appFs "github.com/alexandreh2ag/lets-go-tls/fs"
"github.com/alexandreh2ag/lets-go-tls/types/storage/certificate"
"github.com/stretchr/testify/assert"
"os"
"testing"
)

func Test_createStorage_Success(t *testing.T) {
uid := os.Getuid()
gid := os.Getgid()
ctx := context.TestContext(nil)
want := &fs{id: "foo", fs: ctx.Fs, cfg: ConfigFs{Path: "/app"}, checksum: appFs.NewChecksum(ctx.Fs)}
want := &fs{id: "foo", fs: ctx.Fs, cfg: ConfigFs{Path: "/app"}, checksum: appFs.NewChecksum(ctx.Fs), uid: uid, gid: gid}
cfg := config.StorageConfig{
Id: "foo",
Type: "fs",
Expand All @@ -36,6 +39,8 @@ func Test_createStorage_Fail(t *testing.T) {
}

func TestCreateStorages_Success(t *testing.T) {
uid := os.Getuid()
gid := os.Getgid()
ctx := context.TestContext(nil)
ctx.Config.Storages = []config.StorageConfig{
{
Expand All @@ -44,7 +49,7 @@ func TestCreateStorages_Success(t *testing.T) {
Config: map[string]interface{}{"path": "/app"},
},
}
staticP := &fs{id: "foo", fs: ctx.Fs, cfg: ConfigFs{Path: "/app"}, checksum: appFs.NewChecksum(ctx.Fs)}
staticP := &fs{id: "foo", fs: ctx.Fs, cfg: ConfigFs{Path: "/app"}, checksum: appFs.NewChecksum(ctx.Fs), uid: uid, gid: gid}
want := certificate.Storages{"foo": staticP}
got, err := CreateCertificateStorages(ctx)
assert.NoError(t, err)
Expand Down
24 changes: 23 additions & 1 deletion apps/agent/storage/certificate/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/alexandreh2ag/lets-go-tls/apps/agent/context"
appFs "github.com/alexandreh2ag/lets-go-tls/fs"
"github.com/alexandreh2ag/lets-go-tls/hook"
"github.com/alexandreh2ag/lets-go-tls/os"
"github.com/alexandreh2ag/lets-go-tls/types"
"github.com/alexandreh2ag/lets-go-tls/types/storage/certificate"
"github.com/go-playground/validator/v10"
Expand All @@ -31,6 +32,9 @@ type ConfigFs struct {
OnlyMatchedDomains bool `mapstructure:"only_matched_domains"`
SpecificDomains []ConfigSpecificDomain `mapstructure:"specific_domains" validate:"duplicate_path,dive"`
PostHook *hook.Hook `mapstructure:"post_hook"`

Owner string `mapstructure:"owner"`
Group string `mapstructure:"group"`
}

type ConfigSpecificDomain struct {
Expand All @@ -44,6 +48,9 @@ type fs struct {
fs afero.Fs
checksum *appFs.Checksum
cfg ConfigFs

uid int
gid int
}

func (f fs) ID() string {
Expand Down Expand Up @@ -95,6 +102,12 @@ func (f fs) Save(certificates types.Certificates, hookChan chan<- *hook.Hook) []
errors = append(errors, fmt.Errorf("fail to write key %s: %v", keyPath, err))
continue
}

err = f.fs.Chown(keyPath, f.uid, f.gid)
if err != nil {
errors = append(errors, fmt.Errorf("fail to chown %s: %v", keyPath, err))
continue
}
}

if !f.checksum.MustCompareContentWithPath(cert.Certificate, certPath) {
Expand All @@ -104,6 +117,12 @@ func (f fs) Save(certificates types.Certificates, hookChan chan<- *hook.Hook) []
errors = append(errors, fmt.Errorf("fail to write certificate %s: %v", certPath, err))
continue
}

err = f.fs.Chown(keyPath, f.uid, f.gid)
if err != nil {
errors = append(errors, fmt.Errorf("fail to chown %s: %v", keyPath, err))
continue
}
}

}
Expand Down Expand Up @@ -170,7 +189,10 @@ func createFsStorage(ctx *context.AgentContext, cfg config.StorageConfig) (certi
return nil, err
}

instance := &fs{id: cfg.Id, fs: ctx.Fs, cfg: instanceConfig, checksum: appFs.NewChecksum(ctx.Fs)}
uid := os.GetUserUID(instanceConfig.Owner)
gid := os.GetGroupUID(instanceConfig.Group)

instance := &fs{id: cfg.Id, fs: ctx.Fs, cfg: instanceConfig, checksum: appFs.NewChecksum(ctx.Fs), uid: uid, gid: gid}

return instance, nil
}
Expand Down
52 changes: 50 additions & 2 deletions apps/agent/storage/certificate/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"os"
"path/filepath"
"testing"
)
Expand All @@ -24,7 +25,9 @@ func Test_fs_ID(t *testing.T) {

func Test_createFsStorage(t *testing.T) {
ctx := context.TestContext(nil)
storage := &fs{id: "foo", fs: ctx.Fs, cfg: ConfigFs{Path: "/app"}, checksum: appFs.NewChecksum(ctx.Fs)}
uid := os.Getuid()
gid := os.Getgid()
storage := &fs{id: "foo", fs: ctx.Fs, cfg: ConfigFs{Path: "/app"}, checksum: appFs.NewChecksum(ctx.Fs), uid: uid, gid: gid}
storageSpecificDomains := &fs{
id: "foo",
fs: ctx.Fs,
Expand All @@ -33,6 +36,8 @@ func Test_createFsStorage(t *testing.T) {
{Identifier: "test2", Domains: types.Domains{"example2.com"}},
}},
checksum: appFs.NewChecksum(ctx.Fs),
uid: uid,
gid: gid,
}
tests := []struct {
name string
Expand Down Expand Up @@ -280,7 +285,7 @@ func Test_fs_Save_FailWriteKey(t *testing.T) {
assert.Len(t, errs, 1)
}

func Test_fs_Save_FailCertificateKey(t *testing.T) {
func Test_fs_Save_FailChownKey(t *testing.T) {
ctx := context.TestContext(nil)
ctrl := gomock.NewController(t)
fsMock := mockAfero.NewMockFs(ctrl)
Expand All @@ -292,6 +297,26 @@ func Test_fs_Save_FailCertificateKey(t *testing.T) {
fsMock.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Times(1).Return(nil),
fsMock.EXPECT().Open(gomock.Any()).Times(1).Return(nil, errors.New("error")),
fsMock.EXPECT().OpenFile(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(file, nil),
fsMock.EXPECT().Chown(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(errors.New("fail chown")),
)
storage := &fs{fs: fsMock, cfg: ConfigFs{Path: "/app"}, checksum: appFs.NewChecksum(fsMock)}
errs := storage.Save(certificates, make(chan<- *hook.Hook))
assert.Len(t, errs, 1)
}

func Test_fs_Save_FailWriteCertificate(t *testing.T) {
ctx := context.TestContext(nil)
ctrl := gomock.NewController(t)
fsMock := mockAfero.NewMockFs(ctrl)
certificates := types.Certificates{
{Identifier: "example.com", Key: []byte("key"), Certificate: []byte("certificate")},
}
file, _ := ctx.Fs.Create("/app/test.txt")
gomock.InOrder(
fsMock.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Times(1).Return(nil),
fsMock.EXPECT().Open(gomock.Any()).Times(1).Return(nil, errors.New("error")),
fsMock.EXPECT().OpenFile(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(file, nil),
fsMock.EXPECT().Chown(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil),
fsMock.EXPECT().Open(gomock.Any()).Times(1).Return(nil, errors.New("error")),
fsMock.EXPECT().OpenFile(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil, errors.New("error")),
)
Expand All @@ -300,6 +325,29 @@ func Test_fs_Save_FailCertificateKey(t *testing.T) {
assert.Len(t, errs, 1)
}

func Test_fs_Save_FailChownCertificate(t *testing.T) {
ctx := context.TestContext(nil)
ctrl := gomock.NewController(t)
fsMock := mockAfero.NewMockFs(ctrl)
certificates := types.Certificates{
{Identifier: "example.com", Key: []byte("key"), Certificate: []byte("certificate")},
}
file, _ := ctx.Fs.Create("/app/test.txt")
file2, _ := ctx.Fs.Create("/app/test.txt")
gomock.InOrder(
fsMock.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Times(1).Return(nil),
fsMock.EXPECT().Open(gomock.Any()).Times(1).Return(nil, errors.New("error")),
fsMock.EXPECT().OpenFile(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(file, nil),
fsMock.EXPECT().Chown(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil),
fsMock.EXPECT().Open(gomock.Any()).Times(1).Return(nil, errors.New("error")),
fsMock.EXPECT().OpenFile(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(file2, nil),
fsMock.EXPECT().Chown(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(errors.New("fail chown")),
)
storage := &fs{fs: fsMock, cfg: ConfigFs{Path: "/app"}, checksum: appFs.NewChecksum(fsMock)}
errs := storage.Save(certificates, make(chan<- *hook.Hook))
assert.Len(t, errs, 1)
}

func Test_fs_GetKeyPath(t *testing.T) {
want := "/app/example.com-0.key"
cert := &types.Certificate{Identifier: "example.com-0"}
Expand Down
27 changes: 20 additions & 7 deletions apps/agent/storage/certificate/traefik.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/alexandreh2ag/lets-go-tls/apps/agent/context"
appFs "github.com/alexandreh2ag/lets-go-tls/fs"
"github.com/alexandreh2ag/lets-go-tls/hook"
"github.com/alexandreh2ag/lets-go-tls/os"
"github.com/alexandreh2ag/lets-go-tls/types"
"github.com/alexandreh2ag/lets-go-tls/types/storage/certificate"
"github.com/go-playground/validator/v10"
Expand All @@ -30,13 +31,19 @@ var _ certificate.Storage = &traefik{}
type ConfigTraefik struct {
Path string `mapstructure:"path" validate:"required"`
PrefixFilename string `mapstructure:"prefix_filename"`

Owner string `mapstructure:"owner"`
Group string `mapstructure:"group"`
}

type traefik struct {
id string
fs afero.Fs
checksum *appFs.Checksum
cfg ConfigTraefik

uid int
gid int
}

func (t traefik) ID() string {
Expand Down Expand Up @@ -91,12 +98,15 @@ func (t traefik) WriteCertFile(cert *types.Certificate) error {
content, _ := yaml.Marshal(data)

if !t.checksum.MustCompareContentWithPath(content, path) {
return afero.WriteFile(
t.fs,
path,
content,
0660,
)
errWrite := afero.WriteFile(t.fs, path, content, 0660)
if errWrite != nil {
return fmt.Errorf("fail to write %s: %v", path, errWrite)
}
errChown := t.fs.Chown(path, t.uid, t.gid)
if errChown != nil {
return fmt.Errorf("fail to chown %s: %v", path, errChown)
}

}
return nil
}
Expand All @@ -114,7 +124,10 @@ func createTraefikStorage(ctx *context.AgentContext, cfg config.StorageConfig) (
return nil, err
}

instance := &traefik{id: cfg.Id, fs: ctx.Fs, cfg: instanceConfig, checksum: appFs.NewChecksum(ctx.Fs)}
uid := os.GetUserUID(instanceConfig.Owner)
gid := os.GetGroupUID(instanceConfig.Group)

instance := &traefik{id: cfg.Id, fs: ctx.Fs, cfg: instanceConfig, checksum: appFs.NewChecksum(ctx.Fs), uid: uid, gid: gid}

return instance, nil
}
60 changes: 59 additions & 1 deletion apps/agent/storage/certificate/traefik_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package certificate

import (
"errors"
"fmt"
"github.com/alexandreh2ag/lets-go-tls/apps/agent/config"
"github.com/alexandreh2ag/lets-go-tls/apps/agent/context"
appFs "github.com/alexandreh2ag/lets-go-tls/fs"
Expand All @@ -10,8 +11,10 @@ import (
"github.com/alexandreh2ag/lets-go-tls/types"
"github.com/alexandreh2ag/lets-go-tls/types/storage/certificate"
"github.com/spf13/afero"
"github.com/spf13/afero/mem"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"os"
"path/filepath"
"testing"
)
Expand All @@ -23,7 +26,9 @@ func Test_traefik_ID(t *testing.T) {

func Test_createTraefikStorage(t *testing.T) {
ctx := context.TestContext(nil)
storage := &traefik{id: "foo", fs: ctx.Fs, cfg: ConfigTraefik{Path: "/app"}, checksum: appFs.NewChecksum(ctx.Fs)}
uid := os.Getuid()
gid := os.Getgid()
storage := &traefik{id: "foo", fs: ctx.Fs, cfg: ConfigTraefik{Path: "/app"}, checksum: appFs.NewChecksum(ctx.Fs), uid: uid, gid: gid}
tests := []struct {
name string
cfg config.StorageConfig
Expand Down Expand Up @@ -151,3 +156,56 @@ func Test_traefik_Delete(t1 *testing.T) {
}
assert.Equalf(t1, want, t.Delete(certificates, make(chan<- *hook.Hook)), "Delete(%v)", certificates)
}

func Test_traefik_WriteCertFile(t1 *testing.T) {
cert := &types.Certificate{Identifier: "example.com-0", Certificate: []byte("certificate"), Key: []byte("key")}
ctrl := gomock.NewController(t1)

tests := []struct {
name string
mockFn func(fs *mockAfero.MockFs)
wantErr assert.ErrorAssertionFunc
}{
{
name: "Success",
mockFn: func(fs *mockAfero.MockFs) {
fs.EXPECT().Open(gomock.Any()).Times(1).Return(nil, errors.New("error"))
fs.EXPECT().OpenFile(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(mem.NewFileHandle(mem.CreateFile("/app/file")), nil)
fs.EXPECT().Chown(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil)
},
wantErr: assert.NoError,
},
{
name: "FailWrite",
mockFn: func(fs *mockAfero.MockFs) {
fs.EXPECT().Open(gomock.Any()).Times(1).Return(nil, errors.New("error"))
fs.EXPECT().OpenFile(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil, errors.New("fail write"))
},
wantErr: assert.Error,
},

{
name: "FailChown",
mockFn: func(fs *mockAfero.MockFs) {
fs.EXPECT().Open(gomock.Any()).Times(1).Return(nil, errors.New("error"))
fs.EXPECT().OpenFile(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(mem.NewFileHandle(mem.CreateFile("/app/file")), nil)
fs.EXPECT().Chown(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(errors.New("fail chown"))
},
wantErr: assert.Error,
},
}
for _, tt := range tests {
t1.Run(tt.name, func(t1 *testing.T) {
fsMock := mockAfero.NewMockFs(ctrl)
tt.mockFn(fsMock)
checksum := appFs.NewChecksum(fsMock)
t := traefik{
id: "traefik",
fs: fsMock,
checksum: checksum,
cfg: ConfigTraefik{Path: "/app"},
}
tt.wantErr(t1, t.WriteCertFile(cert), fmt.Sprintf("WriteCertFile(%v)", cert))
})
}
}
4 changes: 4 additions & 0 deletions docs/agent_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ storages:
config:
path: /var/lib/lets-go-tls/ssl # mandatory
prefix_filename: "ssl."
owner: "root"
group: "root"
only_matched_domains: false # when true, only store certificate specified in specific_domains
specific_domains:
- identifier: custom # mandatory
Expand Down Expand Up @@ -105,6 +107,8 @@ storages:
config:
path: /var/lib/traefik/ssl # mandatory
prefix_filename: "ssl."
owner: "root"
group: "root"
```
tree structure example:
Expand Down
6 changes: 6 additions & 0 deletions docs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ func getAgentConfig() agentConfig.Config {
Config: decodeToMap(certificate.ConfigFs{
Path: "/var/lib/lets-go-tls/ssl",
PrefixFilename: "",
Owner: "root",
Group: "root",
SpecificDomains: []certificate.ConfigSpecificDomain{
{Identifier: "custom", Domains: types.Domains{"example.com"}},
{Identifier: "custom", Path: "foo", Domains: types.Domains{"example.com"}},
Expand All @@ -184,6 +186,8 @@ func getAgentConfig() agentConfig.Config {
Config: decodeToMap(certificate.ConfigTraefik{
Path: "/etc/traefik/config",
PrefixFilename: "",
Owner: "root",
Group: "root",
}),
},
{
Expand All @@ -192,6 +196,8 @@ func getAgentConfig() agentConfig.Config {
Config: decodeToMap(certificate.ConfigTraefik{
Path: "/etc/traefik/config",
PrefixFilename: "",
Owner: "root",
Group: "root",
}),
},
}
Expand Down
Loading

0 comments on commit 7d73a70

Please sign in to comment.