osbuild2: support PermitRootLogin in sshd.config stage
Add support for the `PermitRootLogin` option in the `sshd.config` stage. Valid values can be of type `bool` or `string`. Due to this reason, a custom interface type is defined and a custom `UnmarshalJSON()` method is defined for the `SshdConfigConfig` structure. Modify unit tests to test the newly added option and test (un)marhsalling of valid values of both types. Related to https://github.com/osbuild/osbuild/pull/917 Signed-off-by: Tomas Hozza <thozza@redhat.com>
This commit is contained in:
parent
33c7da9dc3
commit
37a39743bc
3 changed files with 190 additions and 4 deletions
|
|
@ -1,9 +1,78 @@
|
|||
package osbuild2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type SshdConfigConfig struct {
|
||||
PasswordAuthentication *bool `json:"PasswordAuthentication,omitempty"`
|
||||
ChallengeResponseAuthentication *bool `json:"ChallengeResponseAuthentication,omitempty"`
|
||||
ClientAliveInterval *int `json:"ClientAliveInterval,omitempty"`
|
||||
PasswordAuthentication *bool `json:"PasswordAuthentication,omitempty"`
|
||||
ChallengeResponseAuthentication *bool `json:"ChallengeResponseAuthentication,omitempty"`
|
||||
ClientAliveInterval *int `json:"ClientAliveInterval,omitempty"`
|
||||
PermitRootLogin PermitRootLoginValue `json:"PermitRootLogin,omitempty"`
|
||||
}
|
||||
|
||||
// PermitRootLoginValue is defined to represent all valid types of the
|
||||
// 'PermitRootLogin' item in the SshdConfigConfig structure.
|
||||
type PermitRootLoginValue interface {
|
||||
isPermitRootLoginValue()
|
||||
}
|
||||
|
||||
// PermitRootLoginValueStr represents a string type of the 'PermitRootLogin'
|
||||
// item in the SshdConfigConfig structure.
|
||||
type PermitRootLoginValueStr string
|
||||
|
||||
func (v PermitRootLoginValueStr) isPermitRootLoginValue() {}
|
||||
|
||||
// PermitRootLoginValueBool represents a bool type of the 'PermitRootLogin'
|
||||
// item in the SshdConfigConfig structure.
|
||||
type PermitRootLoginValueBool bool
|
||||
|
||||
func (v PermitRootLoginValueBool) isPermitRootLoginValue() {}
|
||||
|
||||
// Valid values which can be used for the 'PermitRootLogin' item in
|
||||
// the SshdConfigConfig structure.
|
||||
const (
|
||||
PermitRootLoginValueYes PermitRootLoginValueBool = true
|
||||
PermitRootLoginValueNo PermitRootLoginValueBool = false
|
||||
|
||||
PermitRootLoginValueProhibitPassword PermitRootLoginValueStr = "prohibit-password"
|
||||
PermitRootLoginValueForcedCommandsOnly PermitRootLoginValueStr = "forced-commands-only"
|
||||
)
|
||||
|
||||
// Unexported struct used for Unmarshalling of SshdConfigConfig due to
|
||||
// 'PermitRootLogin' being a boolean or a string.
|
||||
type rawSshdConfigConfig struct {
|
||||
PasswordAuthentication *bool `json:"PasswordAuthentication,omitempty"`
|
||||
ChallengeResponseAuthentication *bool `json:"ChallengeResponseAuthentication,omitempty"`
|
||||
ClientAliveInterval *int `json:"ClientAliveInterval,omitempty"`
|
||||
PermitRootLogin interface{} `json:"PermitRootLogin,omitempty"`
|
||||
}
|
||||
|
||||
func (c *SshdConfigConfig) UnmarshalJSON(data []byte) error {
|
||||
var rawConfig rawSshdConfigConfig
|
||||
if err := json.Unmarshal(data, &rawConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var permitRootLogin PermitRootLoginValue
|
||||
if rawConfig.PermitRootLogin != nil {
|
||||
switch valueType := rawConfig.PermitRootLogin.(type) {
|
||||
case bool:
|
||||
permitRootLogin = PermitRootLoginValueBool(rawConfig.PermitRootLogin.(bool))
|
||||
case string:
|
||||
permitRootLogin = PermitRootLoginValueStr(rawConfig.PermitRootLogin.(string))
|
||||
default:
|
||||
return fmt.Errorf("the 'PermitRootLogin' item has unsupported type %q", valueType)
|
||||
}
|
||||
}
|
||||
|
||||
c.PasswordAuthentication = rawConfig.PasswordAuthentication
|
||||
c.ChallengeResponseAuthentication = rawConfig.ChallengeResponseAuthentication
|
||||
c.ClientAliveInterval = rawConfig.ClientAliveInterval
|
||||
c.PermitRootLogin = permitRootLogin
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type SshdConfigStageOptions struct {
|
||||
|
|
@ -12,7 +81,35 @@ type SshdConfigStageOptions struct {
|
|||
|
||||
func (SshdConfigStageOptions) isStageOptions() {}
|
||||
|
||||
func (o SshdConfigStageOptions) validate() error {
|
||||
if o.Config.PermitRootLogin != nil {
|
||||
value, ok := o.Config.PermitRootLogin.(PermitRootLoginValueStr)
|
||||
if ok {
|
||||
allowedPermitRootLoginStrValues := []PermitRootLoginValueStr{
|
||||
PermitRootLoginValueForcedCommandsOnly,
|
||||
PermitRootLoginValueProhibitPassword,
|
||||
}
|
||||
valid := false
|
||||
for _, validValue := range allowedPermitRootLoginStrValues {
|
||||
if value == validValue {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
return fmt.Errorf("%q is not a valid value for 'PermitRootLogin' option", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSshdConfigStage(options *SshdConfigStageOptions) *Stage {
|
||||
if err := options.validate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &Stage{
|
||||
Type: "org.osbuild.sshd.config",
|
||||
Options: options,
|
||||
|
|
|
|||
|
|
@ -25,13 +25,15 @@ func TestJsonSshdConfigStage(t *testing.T) {
|
|||
PasswordAuthentication: common.BoolToPtr(false),
|
||||
ChallengeResponseAuthentication: common.BoolToPtr(false),
|
||||
ClientAliveInterval: common.IntToPtr(180),
|
||||
PermitRootLogin: PermitRootLoginValueProhibitPassword,
|
||||
},
|
||||
}
|
||||
inputString := `{
|
||||
"config": {
|
||||
"PasswordAuthentication": false,
|
||||
"ChallengeResponseAuthentication": false,
|
||||
"ClientAliveInterval": 180
|
||||
"ClientAliveInterval": 180,
|
||||
"PermitRootLogin": "prohibit-password"
|
||||
}
|
||||
}`
|
||||
var inputOptions SshdConfigStageOptions
|
||||
|
|
@ -51,3 +53,56 @@ func TestJsonSshdConfigStage(t *testing.T) {
|
|||
assert.NoError(t, err, "failed to marshal sshd config into JSON")
|
||||
assert.Equal(t, expectedString, string(inputBytes))
|
||||
}
|
||||
|
||||
func TestSshdConfigStageOptionsValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
options SshdConfigStageOptions
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
name: "empty-options",
|
||||
options: SshdConfigStageOptions{},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
name: "invalid-permit-root-login-str-value",
|
||||
options: SshdConfigStageOptions{
|
||||
Config: SshdConfigConfig{
|
||||
PermitRootLogin: PermitRootLoginValueStr("invalid"),
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "valid-permit-root-login-str-value-1",
|
||||
options: SshdConfigStageOptions{
|
||||
Config: SshdConfigConfig{
|
||||
PermitRootLogin: PermitRootLoginValueForcedCommandsOnly,
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
name: "valid-permit-root-login-str-value-1",
|
||||
options: SshdConfigStageOptions{
|
||||
Config: SshdConfigConfig{
|
||||
PermitRootLogin: PermitRootLoginValueProhibitPassword,
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
}
|
||||
|
||||
for idx, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.err {
|
||||
assert.Errorf(t, tt.options.validate(), "%q didn't return an error [idx: %d]", tt.name, idx)
|
||||
assert.Panics(t, func() { NewSshdConfigStage(&tt.options) })
|
||||
} else {
|
||||
assert.NoErrorf(t, tt.options.validate(), "%q returned an error [idx: %d]", tt.name, idx)
|
||||
assert.NotPanics(t, func() { NewSshdConfigStage(&tt.options) })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -664,6 +664,40 @@ func TestStage_UnmarshalJSON(t *testing.T) {
|
|||
data: []byte(`{"type":"org.osbuild.sshd.config","options":{"config":{}}}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sshd.config-data1",
|
||||
fields: fields{
|
||||
Type: "org.osbuild.sshd.config",
|
||||
Options: &SshdConfigStageOptions{
|
||||
Config: SshdConfigConfig{
|
||||
PasswordAuthentication: common.BoolToPtr(false),
|
||||
ChallengeResponseAuthentication: common.BoolToPtr(false),
|
||||
ClientAliveInterval: common.IntToPtr(42),
|
||||
PermitRootLogin: PermitRootLoginValueNo,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
data: []byte(`{"type":"org.osbuild.sshd.config","options":{"config":{"PasswordAuthentication":false,"ChallengeResponseAuthentication":false,"ClientAliveInterval":42,"PermitRootLogin":false}}}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sshd.config-data2",
|
||||
fields: fields{
|
||||
Type: "org.osbuild.sshd.config",
|
||||
Options: &SshdConfigStageOptions{
|
||||
Config: SshdConfigConfig{
|
||||
PasswordAuthentication: common.BoolToPtr(false),
|
||||
ChallengeResponseAuthentication: common.BoolToPtr(false),
|
||||
ClientAliveInterval: common.IntToPtr(42),
|
||||
PermitRootLogin: PermitRootLoginValueForcedCommandsOnly,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
data: []byte(`{"type":"org.osbuild.sshd.config","options":{"config":{"PasswordAuthentication":false,"ChallengeResponseAuthentication":false,"ClientAliveInterval":42,"PermitRootLogin":"forced-commands-only"}}}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "authconfig",
|
||||
fields: fields{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue