diff --git a/changelog/unreleased/idp-default-files.md b/changelog/unreleased/idp-default-files.md new file mode 100644 index 0000000000..6d710126aa --- /dev/null +++ b/changelog/unreleased/idp-default-files.md @@ -0,0 +1,7 @@ +Enhancement: Generate signing key and encryption secret + +The idp service now automatically generates a signing key and encryption secret when they don't exist. +This will enable service restarts without invalidating existing sessions. + +https://github.com/owncloud/ocis/issues/3909 +https://github.com/owncloud/ocis/pull/4022 diff --git a/extensions/idp/pkg/command/server.go b/extensions/idp/pkg/command/server.go index be0147f8d9..31024e92f2 100644 --- a/extensions/idp/pkg/command/server.go +++ b/extensions/idp/pkg/command/server.go @@ -1,9 +1,18 @@ package command import ( + "bytes" "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" "fmt" + "io" + "io/fs" "os" + "path/filepath" "github.com/oklog/run" "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" @@ -17,6 +26,8 @@ import ( "github.com/urfave/cli/v2" ) +const _rsaKeySize = 4096 + // Server is the entrypoint for the server command. func Server(cfg *config.Config) *cli.Command { return &cli.Command{ @@ -29,6 +40,15 @@ func Server(cfg *config.Config) *cli.Command { fmt.Printf("%v", err) os.Exit(1) } + + if cfg.IDP.EncryptionSecretFile != "" { + if err := ensureEncryptionSecretExists(cfg.IDP.EncryptionSecretFile); err != nil { + return err + } + if err := ensureSigningPrivateKeyExists(cfg.IDP.SigningPrivateKeyFiles); err != nil { + return err + } + } return err }, Action: func(c *cli.Context) error { @@ -102,3 +122,77 @@ func Server(cfg *config.Config) *cli.Command { }, } } + +func ensureEncryptionSecretExists(path string) error { + _, err := os.Stat(path) + if err == nil { + // If the file exists we can just return + return nil + } + if !errors.Is(err, fs.ErrNotExist) { + return err + } + + dir := filepath.Dir(path) + err = os.MkdirAll(dir, 0700) + if err != nil { + return err + } + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + return nil + } + defer f.Close() + + secret := make([]byte, 32) + _, err = rand.Read(secret) + if err != nil { + return err + } + _, err = io.Copy(f, bytes.NewReader(secret)) + if err != nil { + return err + } + + return nil +} + +func ensureSigningPrivateKeyExists(paths []string) error { + for _, path := range paths { + _, err := os.Stat(path) + if err == nil { + // If the file exists we can just return + return nil + } + if !errors.Is(err, fs.ErrNotExist) { + return err + } + + dir := filepath.Dir(path) + err = os.MkdirAll(dir, 0700) + if err != nil { + return err + } + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + return nil + } + defer f.Close() + + pk, err := rsa.GenerateKey(rand.Reader, _rsaKeySize) + if err != nil { + return err + } + + pb := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(pk), + } + if err := pem.Encode(f, pb); err != nil { + return err + } + } + return nil +} diff --git a/extensions/idp/pkg/config/defaults/defaultconfig.go b/extensions/idp/pkg/config/defaults/defaultconfig.go index f67a8b4a4b..2fd213cdfa 100644 --- a/extensions/idp/pkg/config/defaults/defaultconfig.go +++ b/extensions/idp/pkg/config/defaults/defaultconfig.go @@ -1,7 +1,7 @@ package defaults import ( - "path" + "path/filepath" "strings" "github.com/owncloud/ocis/v2/extensions/idp/pkg/config" @@ -24,8 +24,8 @@ func DefaultConfig() *config.Config { Addr: "127.0.0.1:9130", Root: "/", Namespace: "com.owncloud.web", - TLSCert: path.Join(defaults.BaseDataPath(), "idp", "server.crt"), - TLSKey: path.Join(defaults.BaseDataPath(), "idp", "server.key"), + TLSCert: filepath.Join(defaults.BaseDataPath(), "idp", "server.crt"), + TLSKey: filepath.Join(defaults.BaseDataPath(), "idp", "server.key"), TLS: false, }, Reva: &config.Reva{ @@ -47,18 +47,18 @@ func DefaultConfig() *config.Config { AllowScope: nil, AllowClientGuests: false, AllowDynamicClientRegistration: false, - EncryptionSecretFile: "", + EncryptionSecretFile: filepath.Join(defaults.BaseDataPath(), "idp", "encryption.key"), Listen: "", IdentifierClientDisabled: true, - IdentifierClientPath: path.Join(defaults.BaseDataPath(), "idp"), - IdentifierRegistrationConf: path.Join(defaults.BaseDataPath(), "idp", "tmp", "identifier-registration.yaml"), + IdentifierClientPath: filepath.Join(defaults.BaseDataPath(), "idp"), + IdentifierRegistrationConf: filepath.Join(defaults.BaseDataPath(), "idp", "tmp", "identifier-registration.yaml"), IdentifierScopesConf: "", IdentifierDefaultBannerLogo: "", IdentifierDefaultSignInPageText: "", IdentifierDefaultUsernameHintText: "", - SigningKid: "", + SigningKid: "private-key", SigningMethod: "PS256", - SigningPrivateKeyFiles: nil, + SigningPrivateKeyFiles: []string{filepath.Join(defaults.BaseDataPath(), "idp", "private-key.pem")}, ValidationKeysPath: "", CookieBackendURI: "", CookieNames: nil, @@ -124,7 +124,7 @@ func DefaultConfig() *config.Config { }, Ldap: config.Ldap{ URI: "ldaps://localhost:9235", - TLSCACert: path.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), + TLSCACert: filepath.Join(defaults.BaseDataPath(), "idm", "ldap.crt"), BindDN: "uid=idp,ou=sysusers,o=libregraph-idm", BaseDN: "ou=users,o=libregraph-idm", Scope: "sub",