diff --git a/internal/resource/common.go b/internal/resource/common.go index 909963d..26e7c5a 100644 --- a/internal/resource/common.go +++ b/internal/resource/common.go @@ -9,6 +9,7 @@ import ( "path/filepath" "decl/internal/data" "decl/internal/folio" +_ "decl/internal/mapper" "log/slog" "errors" ) @@ -31,7 +32,7 @@ type Common struct { State string `json:"state,omitempty" yaml:"state,omitempty"` config data.ConfigurationValueGetter - Resources data.ResourceMapper `json:"-" yaml:"-"` + Resources folio.ResourceMapper `json:"-" yaml:"-"` Errors []error `json:"-" yaml:"-"` } @@ -53,7 +54,7 @@ func (c *Common) ContentType() string { return c.exttype } -func (c *Common) SetResourceMapper(resources data.ResourceMapper) { +func (c *Common) SetResourceMapper(resources folio.ResourceMapper) { c.Resources = resources } @@ -158,6 +159,7 @@ func (c *Common) IsResourceInconsistent() (result bool) { func (c *Common) AddError(err error) (error) { if err != nil { + slog.Info("Common.AddError()", "errors", c.Errors, "err", err) c.Errors = append(c.Errors, err) } return err diff --git a/internal/resource/container.go b/internal/resource/container.go index ed5b78f..edb5575 100644 --- a/internal/resource/container.go +++ b/internal/resource/container.go @@ -32,15 +32,22 @@ _ "os/exec" "decl/internal/containerlog" "bytes" _ "encoding/base64" + "time" + "errors" ) const ( ContainerTypeName TypeName = "container" ) +var ( + ErrContainerWaitTimeOut = errors.New("Container wait timed out waiting for state") +) + type ContainerClient interface { ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error + ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error ContainerList(context.Context, container.ListOptions) ([]types.Container, error) ContainerInspect(context.Context, string) (types.ContainerJSON, error) ContainerRemove(context.Context, string, container.RemoveOptions) error @@ -91,7 +98,6 @@ type Container struct { Stderr string `json:"stderr,omitempty" yaml:"stderr,omitempty"` apiClient ContainerClient - Resources data.ResourceMapper `json:"-" yaml:"-"` } func init() { @@ -137,10 +143,6 @@ func (c *Container) SetParsedURI(u data.URIParser) (err error) { return } -func (c *Container) SetResourceMapper(resources data.ResourceMapper) { - c.Resources = resources -} - func (c *Container) Clone() data.Resource { return &Container { Id: c.Id, @@ -233,7 +235,6 @@ func (c *Container) Notify(m *machine.EventMessage) { panic(createErr) } case "start_delete": - slog.Info("Container.Notify()", "event", "start_delete") if deleteErr := c.Delete(ctx); deleteErr == nil { if triggerErr := c.StateMachine().Trigger("deleted"); triggerErr == nil { return @@ -245,6 +246,26 @@ func (c *Container) Notify(m *machine.EventMessage) { c.Common.State = "present" panic(deleteErr) } + case "restarting": + if restartErr := c.Restart(ctx); restartErr == nil { + if triggerErr := c.StateMachine().Trigger("restarted"); triggerErr == nil { + return + } else { + _ = c.AddError(triggerErr) + } + } else { + _ = c.AddError(restartErr) + if c.IsResourceInconsistent() { + if triggerErr := c.StateMachine().Trigger("restart-failed"); triggerErr == nil { + panic(restartErr) + } else { + _ = c.AddError(triggerErr) + panic(fmt.Errorf("%w - %w", restartErr, triggerErr)) + } + } + _ = c.StateMachine().Trigger("notexists") + panic(restartErr) + } case "present", "created", "read": c.Common.State = "present" case "running": @@ -475,10 +496,10 @@ func (c *Container) Read(ctx context.Context) ([]byte, error) { func (c *Container) Inspect(ctx context.Context, containerID string) error { containerJSON, err := c.apiClient.ContainerInspect(ctx, containerID) - if client.IsErrNotFound(err) { + if client.IsErrNotFound(err) || containerJSON.State == nil { c.Common.State = "absent" } else { - c.Common.State = "present" + c.Common.State = containerJSON.State.Status c.Id = containerJSON.ID if c.Name == "" { if containerJSON.Name[0] == '/' { @@ -583,3 +604,64 @@ func (c *Container) ResolveId(ctx context.Context) string { } return "" } + +type WaitCondition func(state *types.ContainerState) bool +func (c *Container) wait(ctx context.Context, untilstate WaitCondition) error { + statusCh := make(chan bool) + timeoutCtx, cancel := context.WithTimeout(ctx, 60 * time.Second) + defer cancel() + + go func() { + for { + if containerJSON, err := c.apiClient.ContainerInspect(ctx, c.Id); err == nil { + statusCh <- untilstate(containerJSON.State) + return + } + time.Sleep(2 * time.Second) + } + }() + + select { + case <-timeoutCtx.Done(): + return ErrContainerWaitTimeOut + case <-statusCh: + } + return nil +} + +func (c *Container) Restart(ctx context.Context) (err error) { + if err = c.apiClient.ContainerRestart(ctx, c.Id, container.StopOptions{}); err != nil { + return err + } + if err = c.wait(ctx, func(state *types.ContainerState) bool { + return state.Running + }); err != nil { + return err + } +/* + waitTimeout := 60 * time.Second + interval := 2 * time.Second + deadline := time.Now().Add(waitTimeout) + + for { + if time.Now().After(deadline) { + panic("") + } + if state.Running { + if state.Health != nil { + fmt.Println("Health status:", state.Health.Status) + if state.Health.Status == "healthy" { + fmt.Println("Container is healthy and running.") + break + } + } else { + fmt.Println("Container is running (no health check defined).") + break + } + } + + time.Sleep(interval) + } +*/ + return nil +} diff --git a/internal/resource/container_image.go b/internal/resource/container_image.go index d0ee72e..8747344 100644 --- a/internal/resource/container_image.go +++ b/internal/resource/container_image.go @@ -93,7 +93,6 @@ type ContainerImage struct { outputWriter strings.Builder `json:"-" yaml:"-"` apiClient ContainerImageClient - Resources data.ResourceMapper `json:"-" yaml:"-"` contextDocument data.Document `json:"-" yaml:"-"` ConverterTypes data.TypesRegistry[data.Converter] `json:"-" yaml:"-"` @@ -193,10 +192,6 @@ func (c *ContainerImage) NormalizePath() error { return nil } -func (c *ContainerImage) SetResourceMapper(resources data.ResourceMapper) { - c.Resources = resources -} - func (c *ContainerImage) Clone() data.Resource { return &ContainerImage { Common: c.Common, diff --git a/internal/resource/container_network.go b/internal/resource/container_network.go index 20348a1..12debb6 100644 --- a/internal/resource/container_network.go +++ b/internal/resource/container_network.go @@ -49,7 +49,6 @@ type ContainerNetwork struct { Created time.Time `json:"created" yaml:"created"` apiClient ContainerNetworkClient - Resources data.ResourceMapper `json:"-" yaml:"-"` } func init() { @@ -92,10 +91,6 @@ func (n *ContainerNetwork) NormalizePath() error { return nil } -func (n *ContainerNetwork) SetResourceMapper(resources data.ResourceMapper) { - n.Resources = resources -} - func (n *ContainerNetwork) Clone() data.Resource { return &ContainerNetwork { Common: n.Common.Clone(), diff --git a/internal/resource/exec.go b/internal/resource/exec.go index 20eea02..9ce1ec0 100644 --- a/internal/resource/exec.go +++ b/internal/resource/exec.go @@ -34,8 +34,6 @@ type Exec struct { ReadTemplate *command.Command `yaml:"read,omitempty" json:"read,omitempty"` UpdateTemplate *command.Command `yaml:"update,omitempty" json:"update,omitempty"` DeleteTemplate *command.Command `yaml:"delete,omitempty" json:"delete,omitempty"` - - Resources data.ResourceMapper `yaml:"-" json:"-"` } func init() { @@ -63,10 +61,6 @@ func NewExec() *Exec { return e } -func (x *Exec) SetResourceMapper(resources data.ResourceMapper) { - x.Resources = resources -} - func (x *Exec) Clone() data.Resource { return &Exec { Common: x.Common.Clone(), diff --git a/internal/resource/group.go b/internal/resource/group.go index 202a3ce..9dd3573 100644 --- a/internal/resource/group.go +++ b/internal/resource/group.go @@ -48,7 +48,6 @@ type Group struct { ReadCommand *command.Command `json:"-" yaml:"-"` UpdateCommand *command.Command `json:"-" yaml:"-"` DeleteCommand *command.Command `json:"-" yaml:"-"` - Resources data.ResourceMapper `json:"-" yaml:"-"` groupStatus *user.Group `json:"-" yaml:"-"` } @@ -102,10 +101,6 @@ func (g *Group) NormalizePath() error { return nil } -func (g *Group) SetResourceMapper(resources data.ResourceMapper) { - g.Resources = resources -} - func (g *Group) Clone() data.Resource { newg := &Group { Common: g.Common, diff --git a/internal/resource/http.go b/internal/resource/http.go index 4ae0f58..97c268d 100644 --- a/internal/resource/http.go +++ b/internal/resource/http.go @@ -91,7 +91,6 @@ type HTTP struct { LastModified time.Time `json:"lastmodified,omitempty" yaml:"lastmodified,omitempty"` Size int64 `yaml:"size,omitempty" json:"size,omitempty"` SignatureValue string `yaml:"signature,omitempty" json:"signature,omitempty"` - Resources data.ResourceMapper `yaml:"-" json:"-"` reader *transport.Reader `yaml:"-" json:"-"` writer *transport.ReadWriter `yaml:"-" json:"-"` } @@ -127,10 +126,6 @@ func (h *HTTP) NormalizePath() error { return nil } -func (h *HTTP) SetResourceMapper(resources data.ResourceMapper) { - h.Resources = resources -} - func (h *HTTP) Open() (err error) { u := h.Common.parsedURI if u == nil { diff --git a/internal/resource/iptables.go b/internal/resource/iptables.go index 9f2b140..b5b3539 100644 --- a/internal/resource/iptables.go +++ b/internal/resource/iptables.go @@ -136,8 +136,6 @@ type Iptable struct { ReadChainCommand *command.Command `yaml:"-" json:"-"` UpdateCommand *command.Command `yaml:"-" json:"-"` DeleteCommand *command.Command `yaml:"-" json:"-"` - - Resources data.ResourceMapper `yaml:"-" json:"-"` } @@ -185,10 +183,6 @@ func (i *Iptable) Init(u data.URIParser) (err error) { return } -func (i *Iptable) SetResourceMapper(resources data.ResourceMapper) { - i.Resources = resources -} - func (i *Iptable) Clone() data.Resource { newIpt := &Iptable { Common: i.Common, @@ -286,13 +280,13 @@ func (i *Iptable) Notify(m *machine.EventMessage) { // Set the chain ID and update the mapped URI func (i *Iptable) SetId(id uint) { if i.Id != id { - uri := i.URI() + uri := folio.URI(i.URI()) i.Id = id decl, ok := i.Resources.Get(uri) if ok { i.Resources.Delete(uri) } - i.Resources.Set(i.URI(), decl) + i.Resources.Set(folio.URI(i.URI()), decl) } } diff --git a/internal/resource/iptables_test.go b/internal/resource/iptables_test.go index 6e9e35c..f644ae5 100644 --- a/internal/resource/iptables_test.go +++ b/internal/resource/iptables_test.go @@ -20,7 +20,7 @@ _ "syscall" "testing" _ "time" "decl/internal/command" - "decl/internal/data" + "decl/internal/folio" ) func TestNewIptableResource(t *testing.T) { @@ -167,7 +167,7 @@ func TestIptableRuleExtractorById(t *testing.T) { func TestIptableRuleExtractorByFlags(t *testing.T) { ipt := NewIptable() - ipt.Resources = data.NewResourceMapper() + ipt.Resources = folio.NewResourceMapper() assert.NotNil(t, ipt) ipt.Table = IptableName("filter") ipt.Chain = IptableChain("FOO") diff --git a/internal/resource/network_route.go b/internal/resource/network_route.go index b2670aa..bb90fc2 100644 --- a/internal/resource/network_route.go +++ b/internal/resource/network_route.go @@ -96,8 +96,6 @@ type NetworkRoute struct { ReadCommand *command.Command `yaml:"-" json:"-"` UpdateCommand *command.Command `yaml:"-" json:"-"` DeleteCommand *command.Command `yaml:"-" json:"-"` - - Resources data.ResourceMapper `json:"-" yaml:"-"` } func NewNetworkRoute() *NetworkRoute { @@ -119,10 +117,6 @@ func (n *NetworkRoute) NormalizePath() error { return nil } -func (n *NetworkRoute) SetResourceMapper(resources data.ResourceMapper) { - n.Resources = resources -} - func (n *NetworkRoute) Clone() data.Resource { newn := &NetworkRoute { Common: n.Common, diff --git a/internal/resource/openpgp_keyring.go b/internal/resource/openpgp_keyring.go index 1cf3e43..2c8b680 100644 --- a/internal/resource/openpgp_keyring.go +++ b/internal/resource/openpgp_keyring.go @@ -28,6 +28,7 @@ import ( var ( ErrOpenPGPEncryptionFailure error = errors.New("OpenPGP encryption failure") + ErrOpenPGPDecryptionFailure error = errors.New("OpenPGP decryption failure") ) const ( @@ -48,14 +49,14 @@ func init() { type OpenPGPKeyRing struct { - *Common `json:",inline" yaml:",inline"` - stater machine.Stater `json:"-" yaml:"-"` - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Comment string `json:"comment,omitempty" yaml:"comment,omitempty"` - Email string `json:"email,omitempty" yaml:"email,omitempty"` - KeyRing string `json:"keyring,omitempty" yaml:"keyring,omitempty"` - Bits int `json:"bits" yaml:"bits"` - KeyRingRef folio.ResourceReference `json:"keyringref,omitempty" yaml:"keyringref,omitempty"` + *Common `json:",inline" yaml:",inline"` + stater machine.Stater `json:"-" yaml:"-"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Comment string `json:"comment,omitempty" yaml:"comment,omitempty"` + Email string `json:"email,omitempty" yaml:"email,omitempty"` + KeyRing string `json:"keyring,omitempty" yaml:"keyring,omitempty"` + Bits int `json:"bits" yaml:"bits"` + KeyRingRef folio.ResourceReference `json:"keyringref,omitempty" yaml:"keyringref,omitempty"` entityList openpgp.EntityList } @@ -65,6 +66,7 @@ func NewOpenPGPKeyRing() (o *OpenPGPKeyRing) { Common: NewCommon(OpenPGPKeyRingTypeName, false), Bits: 2048, } + o.Common.NormalizePath = o.NormalizePath return } @@ -81,7 +83,6 @@ func (o *OpenPGPKeyRing) NormalizePath() error { return nil } - func (o *OpenPGPKeyRing) Validate() (err error) { var keyringJson []byte if keyringJson, err = o.JSON(); err == nil { @@ -124,8 +125,29 @@ func (o *OpenPGPKeyRing) EncryptPrivateKey(entity *openpgp.Entity) error { return nil } +func (o *OpenPGPKeyRing) DecryptPrivateKey(entity *openpgp.Entity) error { + if o.config != nil && entity.PrivateKey != nil { + passphraseConfig, _ := o.config.GetValue("passphrase") + passphrase := []byte(passphraseConfig.(string)) + if len(passphrase) > 0 { + if decryptErr := entity.PrivateKey.Decrypt(passphrase); decryptErr != nil { + return fmt.Errorf("%w private key: %w", ErrOpenPGPDecryptionFailure, decryptErr) + } + for _, subkey := range entity.Subkeys { + if decryptErr := subkey.PrivateKey.Encrypt(passphrase); decryptErr != nil { + return fmt.Errorf("%w subkey (private key): %w", ErrOpenPGPDecryptionFailure, decryptErr) + } + } + } + } + return nil +} + func (o *OpenPGPKeyRing) Create(ctx context.Context) (err error) { var entity *openpgp.Entity + var keyRingFileStream io.WriteCloser + var keyRingBuffer bytes.Buffer + cfg := o.Config() entity, err = openpgp.NewEntity(o.Name, o.Comment, o.Email, cfg) o.entityList = append(o.entityList, entity) @@ -142,25 +164,37 @@ func (o *OpenPGPKeyRing) Create(ctx context.Context) (err error) { return } - if len(o.KeyRing) == 0 { - var keyringBuffer bytes.Buffer - if publicKeyWriter, err := armor.Encode(&keyringBuffer, openpgp.PublicKeyType, nil); err == nil { - if err = entity.Serialize(publicKeyWriter); err == nil { - - } - publicKeyWriter.Close() - } - keyringBuffer.WriteString("\n") - if privateKeyWriter, err := armor.Encode(&keyringBuffer, openpgp.PrivateKeyType, nil); err == nil { + if len(o.KeyRing) == 0 { // XXX this should probably always overwrite the value of KeyRing + if privateKeyWriter, err := armor.Encode(&keyRingBuffer, openpgp.PrivateKeyType, nil); err == nil { if err = entity.SerializePrivateWithoutSigning(privateKeyWriter, nil); err == nil { + } else { + slog.Error("Failed writing privatekey", "err", err) } privateKeyWriter.Close() + keyRingBuffer.WriteString("\n") + } + if publicKeyWriter, err := armor.Encode(&keyRingBuffer, openpgp.PublicKeyType, nil); err == nil { + if err = entity.Serialize(publicKeyWriter); err == nil { + + } else { + slog.Error("Failed writing public key", "err", err) + } + publicKeyWriter.Close() + keyRingBuffer.WriteString("\n") } - keyringBuffer.WriteString("\n") - o.KeyRing = keyringBuffer.String() + o.KeyRing = keyRingBuffer.String() } + + if len(o.KeyRingRef) > 0 { + keyRingFileStream, _ = o.KeyRingRef.Lookup(o.Resources).ContentWriterStream() + defer keyRingFileStream.Close() + if _, writeErr := keyRingFileStream.Write([]byte(o.KeyRing)); writeErr != nil { + err = writeErr + } + } + return } @@ -333,6 +367,8 @@ func (o *OpenPGPKeyRing) ResolveId(ctx context.Context) string { if e := o.NormalizePath(); e != nil { panic(e) } + o.GetIdentityFromKeyRing() + o.Common.Path = fmt.Sprintf("%s/%s/%s", o.Name, o.Comment, o.Email) return o.Common.Path } @@ -363,6 +399,33 @@ func (o *OpenPGPKeyRing) ReadStat() (err error) { return } +func (o *OpenPGPKeyRing) GetIdentityFromKeyRing() error { + if len(o.KeyRing) > 0 { + if keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(o.KeyRing))); err != nil { + return err + } else { + for _, entity := range keyring { + for identityName, identity := range entity.Identities { + slog.Info("OpenPGPKeyRing.GetIdentityFromKeyRing()", "identity", identityName) + if identity.UserId != nil { + if len(identity.UserId.Name) > 0 { + o.Name = identity.UserId.Name + } + if len(identity.UserId.Comment) > 0 { + o.Comment = identity.UserId.Comment + } + if len(identity.UserId.Email) > 0 { + o.Email = identity.UserId.Email + } + return nil + } + } + } + } + } + return nil +} + func (o *OpenPGPKeyRing) Read(ctx context.Context) (yamlData []byte, err error) { var keyringReader io.ReadCloser statErr := o.ReadStat() @@ -371,11 +434,19 @@ func (o *OpenPGPKeyRing) Read(ctx context.Context) (yamlData []byte, err error) } if keyringReader, err = o.GetContent(nil); err == nil { + defer keyringReader.Close() if krData, krErr := io.ReadAll(keyringReader); krErr == nil { o.KeyRing = string(krData) o.entityList, err = openpgp.ReadArmoredKeyRing(strings.NewReader(o.KeyRing)) } else { - err = krErr + return nil, fmt.Errorf("%w - %w: %s", ErrResourceStateAbsent, krErr, o.Path) + } + } + o.GetIdentityFromKeyRing() + + for i := range o.entityList { + if decryptErr := o.DecryptPrivateKey(o.entityList[i]); decryptErr != nil { + return nil, fmt.Errorf("%w - %w: %s", ErrResourceStateAbsent, decryptErr, o.Path) } } @@ -408,19 +479,24 @@ func (o *OpenPGPKeyRing) readThru() (contentReader io.ReadCloser, err error) { return } -func (o *OpenPGPKeyRing) URI() string { return string(o.Common.URI()) } +func (o *OpenPGPKeyRing) URI() string { + return fmt.Sprintf("%s://%s/%s/%s", o.Type(), o.Name, o.Comment, o.Email) +} -func (o *OpenPGPKeyRing) Type() string { return "openpgp-keyring" } +func (o *OpenPGPKeyRing) Type() string { return string(OpenPGPKeyRingTypeName) } func (o *OpenPGPKeyRing) ContentReaderStream() (*transport.Reader, error) { - if len(o.KeyRing) == 0 && ! o.KeyRingRef.IsEmpty() { + //if len(o.KeyRing) == 0 && ! o.KeyRingRef.IsEmpty() { + if ! o.KeyRingRef.IsEmpty() { + slog.Info("OpenPGPKeyRing.ContentReaderStream()", "keyring", o) return o.KeyRingRef.Lookup(nil).ContentReaderStream() } return nil, fmt.Errorf("Cannot provide transport reader for string content") } func (o *OpenPGPKeyRing) ContentWriterStream() (*transport.Writer, error) { - if len(o.KeyRing) == 0 && ! o.KeyRingRef.IsEmpty() { + //if len(o.KeyRing) == 0 && ! o.KeyRingRef.IsEmpty() { + if ! o.KeyRingRef.IsEmpty() { return o.KeyRingRef.Lookup(nil).ContentWriterStream() } return nil, fmt.Errorf("Cannot provide transport writer for string content") diff --git a/internal/resource/openpgp_keyring_test.go b/internal/resource/openpgp_keyring_test.go index 2f6bcf6..1ac1640 100644 --- a/internal/resource/openpgp_keyring_test.go +++ b/internal/resource/openpgp_keyring_test.go @@ -8,6 +8,112 @@ import ( "testing" "fmt" "decl/internal/data" + "decl/internal/codec" + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" + "bytes" + "log/slog" + "io" + "os" + "strings" + "path/filepath" + "io/ioutil" +) + +var( + TestUserKeyPublic string = ` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBGhI/VUBCADDVSm3mKY5JsncMMJFV0zELMrmip7dkK3vvMvVWVmMHiC4akDH +WPxUdWNQPjE2e5HGF9Ebg0c7gu634VG470MnTzFdPV6f5zA+yJfdKrLq7gpl9QGW +jFLIeK/l4xc+MIpOE1rD9WqYUUw2IYY8YANYq4yB36rq41VuZps/adI9Go5IhfcU +3SVb7o7pa/gWE0FVu9ze31j2agC8FIKHgB++7bmYgbAQz5Qi1qgtG0Kn25QUacJ6 +Akm2+h4w3SQCR6HLRV2BO29x9mFBszf2KQ7DW2VNiGyUuQQ3m8v2ZidG/11ff6U6 +ad5tvr/8sYr5jOnKEJyDP9v9yQ04cU94GmsPABEBAAHNL1Rlc3RVc2VyMSAoVGVz +dFVzZXIxKSA8dGVzdHVzZXJAcm9zc2tlZW4uaG91c2U+wsCKBBMBCAA+BQJoSP1V +CRDfB5I2FtNl1xYhBADOnA8Aah3FXyl/m98HkjYW02XXAhsDAh4BAhkBAgsHAhUI +AxYAAgMnBwIAAGKLB/9whwgfLkoTYI+Q0Te2uZXl7FPyW0t4tbUSoI1aiVw+ymND +V+wuqYsjYDob2MyK/TAFkSCqZhBIRbGLRJtwzQwkF/amGuelHSSBX3LdxK/sp2UU ++zv/NBEP1LlUNKchuxpdBPYjKHdbOLPoKqFfdCujSxpWTIeKCwvnDtuP+PlsjUgc +afxMx+cVwe77AZ1Fi5CBD9Dr7nNsLovywrSiszyVh5eT0pbcu8Elf5oYQNT1q3ms +aqrw95shlGQiwVgrfTvRCDeZnKafHwMUoh48otsNI9aS0HpChi+JNd+PLe/Wlsj5 +x+q0sHc+5/24zQvN0Wx9fPE4/7z8Pr+CUhAxjI7hzsBNBGhI/VUBCACfahoi2GRx +OfOqJ4ZN8Te+4bJeOQdzTofAisfCU736q+MvEKtAuY72vzWdERWpn7XABmtkfII8 +Xyya8iHKuEGCETy0YoZ15GLBgSHhLnlN2U/4BkrUjGvUjZWquW22gfFB1q8ZdSGw +pg8hrspY/b+nnAGdawq+hfE6YRQTmG5tkBrctxfxglBnKzezmL1a3arTHWf2SRlL +eAWNI4sACryP/5plD6+9Z9YXTKeDlXnga6BzB80y7F2g0cPdtX20dWAGx4irq2I2 +SxlABvWMZrdm2Upr6yLHD2zTpbf1FkKFUw9j0lrL1fMq0/j9zbKJsTh6CtOlF04s +AeoEV9SZ36GvABEBAAHCwHYEGAEIACoFAmhI/VUJEN8HkjYW02XXFiEEAM6cDwBq +HcVfKX+b3weSNhbTZdcCGwwAAFxWB/9Af7qpYKldDz5iNYQluFoPfyHAVlOpiakf +hvnu7hijTEgB1frByKu2m+k4m7gANBgfWldsYi1feWBVn6fspXuCc0k7opmQgol7 +ueTrsGnNB7U0l14HcKSTLZ2c/Jt7ZS6aXXjxpYPq0/5lt8+5sRkXxHI9HhAx6gK/ +RJCNITRt/V/gT4YWWbz8VHgkPIfHFsjr8brVhm4V/+JKZAG4eSNQp4QrTpdIjdfa +1rd91qffyrWhPNTQErE2jFOXSU/OojdDjGJ7pwr0qj4FHACVL3/oqYog2NrUWf92 +QcwwWrEZNZUpJEMUB5q+tOoZ40ll0A8lsvABULZDqWveYpRQF+rQ +=jBvZ +-----END PGP PUBLIC KEY BLOCK----- +` + TestUserKeyPair string = ` +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=BAfD +-----END PGP PRIVATE KEY BLOCK----- +` ) func TestNewOpenPGPKeyRingResource(t *testing.T) { @@ -106,3 +212,81 @@ func TestReadKeyRing(t *testing.T) { assert.Contains(t, testKeyRing.entityList[0].Identities, "TestUser (TestUser) ") } + +func TestDecryptKeyPairRing(t *testing.T) { + keyReader := bytes.NewBufferString(TestUserKeyPair) + block, err := armor.Decode(keyReader) + + assert.NotEqual(t, err, io.EOF) + assert.Nil(t, err) + assert.False(t, block.Type != openpgp.PublicKeyType && block.Type != openpgp.PrivateKeyType) + + slog.Info("TestDecryptKeyPairRing", "block", block) + + entityList, err := openpgp.ReadKeyRing(block.Body) + assert.True(t, entityList[0].PrivateKey.Encrypted) + +// entityList, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(TestUserKeyPair)) + slog.Info("TestDecryptKeyPairRing", "entity", entityList[0], "err", err) + assert.Nil(t, err) + assert.NotNil(t, entityList) + + entityList[0].PrivateKey.Decrypt([]byte("foo")) + assert.False(t, entityList[0].PrivateKey.Encrypted) + +/* + for _, subkey := range entity.Subkeys { + if decryptErr := subkey.PrivateKey.Encrypt(passphrase); decryptErr != nil { + return fmt.Errorf("%w subkey (private key): %w", ErrOpenPGPDecryptionFailure, decryptErr) + } + } +*/ +} + +func TestReadKeyPairRing(t *testing.T) { + ctx := context.Background() + + testDataDir := "../../tests/data/openpgp-keyring" + files, err := os.ReadDir(testDataDir) + assert.Nil(t, err) + + for _, entry := range files { + if entry.IsDir() { + continue + } + if ! strings.HasSuffix(strings.ToLower(entry.Name()), ".asc") { + continue + } + + path := filepath.Join(testDataDir, entry.Name()) + fileData, err := ioutil.ReadFile(path) + + declarationAttributes := strings.Builder{} + enc := codec.NewYAMLEncoder(&declarationAttributes) + type kr struct { + KeyRing string `yaml:"keyring"` + } + assert.Nil(t, enc.Encode(&kr{ KeyRing: string(fileData) })) + + testKeyRing := NewOpenPGPKeyRing() + + testKeyRing.UseConfig(MockConfigValueGetter(func(key string) (any, error) { + switch key { + case "passphrase": + return "foo", nil + } + return nil, fmt.Errorf("%w: %s", data.ErrUnknownConfigurationKey, key) + })) + + e := testKeyRing.LoadDecl(declarationAttributes.String()) + assert.Nil(t, e) + y, err := testKeyRing.Read(ctx) + assert.Nil(t, err) + assert.NotNil(t, y) + assert.Greater(t, len(testKeyRing.entityList), 0) + + if strings.HasSuffix(strings.ToLower(entry.Name()), "_private.asc") { + assert.NotNil(t, testKeyRing.entityList[0].PrivateKey) + } + } +} diff --git a/internal/resource/package.go b/internal/resource/package.go index d3806c6..1fab088 100644 --- a/internal/resource/package.go +++ b/internal/resource/package.go @@ -66,7 +66,6 @@ type Package struct { ReadCommand *command.Command `yaml:"-" json:"-"` UpdateCommand *command.Command `yaml:"-" json:"-"` DeleteCommand *command.Command `yaml:"-" json:"-"` - Resources data.ResourceMapper `yaml:"-" json:"-"` } func init() { @@ -123,10 +122,6 @@ func (p *Package) Init(u data.URIParser) error { return p.SetParsedURI(u) } -func (p *Package) SetResourceMapper(resources data.ResourceMapper) { - p.Resources = resources -} - func (p *Package) Clone() data.Resource { newp := &Package { Common: p.Common.Clone(), diff --git a/internal/resource/pki.go b/internal/resource/pki.go index 0d53465..9825e85 100644 --- a/internal/resource/pki.go +++ b/internal/resource/pki.go @@ -88,7 +88,6 @@ type PKI struct { Bits int `json:"bits" yaml:"bits"` EncodingType EncodingType `json:"type" yaml:"type"` - Resources data.ResourceMapper `json:"-" yaml:"-"` } func NewPKI() *PKI { @@ -96,11 +95,6 @@ func NewPKI() *PKI { return p } -func (k *PKI) SetResourceMapper(resources data.ResourceMapper) { - slog.Info("PKI.SetResourceMapper()", "resources", resources) - k.Resources = resources -} - func (k *PKI) Clone() data.Resource { return &PKI { Common: k.Common.Clone(), diff --git a/internal/resource/user.go b/internal/resource/user.go index b650fa3..2790a7f 100644 --- a/internal/resource/user.go +++ b/internal/resource/user.go @@ -60,7 +60,6 @@ type User struct { ReadCommand *command.Command `json:"-" yaml:"-"` UpdateCommand *command.Command `json:"-" yaml:"-"` DeleteCommand *command.Command `json:"-" yaml:"-"` - Resources data.ResourceMapper `json:"-" yaml:"-"` } func NewUser() *User { @@ -107,10 +106,6 @@ func (u *User) NormalizePath() error { return nil } -func (u *User) SetResourceMapper(resources data.ResourceMapper) { - u.Resources = resources -} - func (u *User) Clone() data.Resource { newu := &User { Common: u.Common,