diff --git a/internal/osbuild2/sshd_config_stage.go b/internal/osbuild2/sshd_config_stage.go new file mode 100644 index 000000000..30d2afa4a --- /dev/null +++ b/internal/osbuild2/sshd_config_stage.go @@ -0,0 +1,20 @@ +package osbuild2 + +type SshdConfigConfig struct { + PasswordAuthentication *bool `json:"PasswordAuthentication,omitempty"` + ChallengeResponseAuthentication *bool `json:"ChallengeResponseAuthentication,omitempty"` + ClientAliveInterval *int `json:"ClientAliveInterval,omitempty"` +} + +type SshdConfigStageOptions struct { + Config SshdConfigConfig `json:"config"` +} + +func (SshdConfigStageOptions) isStageOptions() {} + +func NewSshdConfigStage(options *SshdConfigStageOptions) *Stage { + return &Stage{ + Type: "org.osbuild.sshd.config", + Options: options, + } +} diff --git a/internal/osbuild2/sshd_config_stage_test.go b/internal/osbuild2/sshd_config_stage_test.go new file mode 100644 index 000000000..f59d027b4 --- /dev/null +++ b/internal/osbuild2/sshd_config_stage_test.go @@ -0,0 +1,53 @@ +package osbuild2 + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/osbuild/osbuild-composer/internal/common" + "github.com/stretchr/testify/assert" +) + +func TestNewSshdConfigStage(t *testing.T) { + expectedStage := &Stage{ + Type: "org.osbuild.sshd.config", + Options: &SshdConfigStageOptions{}, + } + actualStage := NewSshdConfigStage(&SshdConfigStageOptions{}) + assert.Equal(t, expectedStage, actualStage) +} + +func TestJsonSshdConfigStage(t *testing.T) { + // First test that the JSON can be parsed into the expected structure. + expectedOptions := SshdConfigStageOptions{ + Config: SshdConfigConfig{ + PasswordAuthentication: common.BoolToPtr(false), + ChallengeResponseAuthentication: common.BoolToPtr(false), + ClientAliveInterval: common.IntToPtr(180), + }, + } + inputString := `{ + "config": { + "PasswordAuthentication": false, + "ChallengeResponseAuthentication": false, + "ClientAliveInterval": 180 + } + }` + var inputOptions SshdConfigStageOptions + err := json.Unmarshal([]byte(inputString), &inputOptions) + assert.NoError(t, err, "failed to parse JSON into sshd config") + assert.True(t, reflect.DeepEqual(expectedOptions, inputOptions)) + + // Second try the other way around with stress on missing values + // for those parameters that the user didn't specify. + inputOptions = SshdConfigStageOptions{ + Config: SshdConfigConfig{ + PasswordAuthentication: common.BoolToPtr(true), + }, + } + expectedString := `{"config":{"PasswordAuthentication":true}}` + inputBytes, err := json.Marshal(inputOptions) + assert.NoError(t, err, "failed to marshal sshd config into JSON") + assert.Equal(t, expectedString, string(inputBytes)) +} diff --git a/internal/osbuild2/stage.go b/internal/osbuild2/stage.go index bc88dbdf4..6e6fc4bc0 100644 --- a/internal/osbuild2/stage.go +++ b/internal/osbuild2/stage.go @@ -146,6 +146,8 @@ func (stage *Stage) UnmarshalJSON(data []byte) error { // The stage accepts also source input, but we need to rework all inputs first to handle this nicely here. // Only files input is used by the XZ stage at this moment. inputs = new(FilesInputs) + case "org.osbuild.sshd.config": + options = new(SshdConfigStageOptions) default: return fmt.Errorf("unexpected stage type: %s", rawStage.Type) } diff --git a/internal/osbuild2/stage_test.go b/internal/osbuild2/stage_test.go index 0789fcb34..bd318921c 100644 --- a/internal/osbuild2/stage_test.go +++ b/internal/osbuild2/stage_test.go @@ -637,6 +637,16 @@ func TestStage_UnmarshalJSON(t *testing.T) { data: []byte(`{"type":"org.osbuild.users","options":{"users":null}}`), }, }, + { + name: "sshd.config", + fields: fields{ + Type: "org.osbuild.sshd.config", + Options: &SshdConfigStageOptions{}, + }, + args: args{ + data: []byte(`{"type":"org.osbuild.sshd.config","options":{"config":{}}}`), + }, + }, } for idx, tt := range tests { t.Run(tt.name, func(t *testing.T) {