Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v17] backport netiq implementation #51541

Merged
merged 7 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions api/proto/teleport/legacy/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6455,6 +6455,8 @@ message PluginSpecV1 {
PluginEmailSettings email = 17;
// Settings for the Microsoft Teams plugin
PluginMSTeamsSettings msteams = 18;
// Settings for the OpenTex NetIQ plugin.
PluginNetIQSettings net_iq = 19;
}

// generation contains a unique ID that should:
Expand Down Expand Up @@ -6924,6 +6926,18 @@ message PluginMSTeamsSettings {
string default_recipient = 5;
}

// PluginNetIQSettings defines the settings for a NetIQ integration plugin
message PluginNetIQSettings {
option (gogoproto.equal) = true;
// oauth_issuer_endpoint is the NetIQ Oauth Issuer endpoint.
// Usually, it's equal to https://osp.domain.ext/a/idm/auth/oauth2
string oauth_issuer_endpoint = 1;
// api_endpoint is the IDM PROV Rest API location.
string api_endpoint = 2;
// insecure_skip_verify controls whether the NetIQ certificate validation should be skipped.
bool insecure_skip_verify = 3;
}

message PluginBootstrapCredentialsV1 {
oneof credentials {
PluginOAuth2AuthorizationCodeCredentials oauth2_authorization_code = 1;
Expand Down Expand Up @@ -6962,6 +6976,8 @@ message PluginStatusV1 {
PluginOktaStatusV1 okta = 7;
// AWSIC holds status details for the AWS Identity Center plugin.
PluginAWSICStatusV1 aws_ic = 8;
// NetIQ holds status details for the NetIQ plugin.
PluginNetIQStatusV1 net_iq = 9;
}

// last_raw_error variable stores the most recent raw error message received from an API or service.
Expand All @@ -6971,6 +6987,18 @@ message PluginStatusV1 {
string last_raw_error = 6;
}

// PluginNetIQStatusV1 is the status details for the NetIQ plugin.
message PluginNetIQStatusV1 {
// imported_users is the number of users imported from NetIQ eDirectory.
uint32 imported_users = 1;
// imported_groups is the number of groups imported from NetIQ eDirectory.
uint32 imported_groups = 2;
// imported_roles is the number of roles imported from NetIQ eDirectory.
uint32 imported_roles = 3;
// imported_resources is the number of resources imported from NetIQ eDirectory.
uint32 imported_resources = 4;
}

// PluginGitlabStatusV1 is the status details for the Gitlab plugin.
message PluginGitlabStatusV1 {
// imported_users is the number of users imported from Gitlab.
Expand Down
39 changes: 39 additions & 0 deletions api/types/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package types

import (
"net/url"
"time"

"github.com/gravitational/trace"
Expand Down Expand Up @@ -83,6 +84,8 @@ const (
PluginTypeEmail = "email"
// PluginTypeMSTeams indicates a Microsoft Teams integration
PluginTypeMSTeams = "msteams"
// PluginTypeNetIQ indicates a NetIQ integration
PluginTypeNetIQ = "netiq"
)

// PluginSubkind represents the type of the plugin, e.g., access request, MDM etc.
Expand Down Expand Up @@ -131,6 +134,7 @@ type PluginStatus interface {
GetEntraId() *PluginEntraIDStatusV1
GetOkta() *PluginOktaStatusV1
GetAwsIc() *PluginAWSICStatusV1
GetNetIq() *PluginNetIQStatusV1
}

// NewPluginV1 creates a new PluginV1 resource.
Expand Down Expand Up @@ -377,6 +381,20 @@ func (p *PluginV1) CheckAndSetDefaults() error {
if len(staticCreds.Labels) == 0 {
return trace.BadParameter("labels must be specified")
}
case *PluginSpecV1_NetIq:
if settings.NetIq == nil {
return trace.BadParameter("missing NetIQ settings")
}
if err := settings.NetIq.Validate(); err != nil {
return trace.Wrap(err)
}
staticCreds := p.Credentials.GetStaticCredentialsRef()
if staticCreds == nil {
return trace.BadParameter("NetIQ plugin must be used with the static credentials ref type")
}
if len(staticCreds.Labels) == 0 {
return trace.BadParameter("labels must be specified")
}
default:
return nil
}
Expand Down Expand Up @@ -547,6 +565,8 @@ func (p *PluginV1) GetType() PluginType {
return PluginTypeEmail
case *PluginSpecV1_Msteams:
return PluginTypeMSTeams
case *PluginSpecV1_NetIq:
return PluginTypeNetIQ
default:
return PluginTypeUnknown
}
Expand Down Expand Up @@ -843,3 +863,22 @@ func (c *PluginGitlabSettings) Validate() error {

return nil
}

func (c *PluginNetIQSettings) Validate() error {
if c.OauthIssuerEndpoint == "" {
return trace.BadParameter("oauth_issuer endpoint must be set")
}

if _, err := url.Parse(c.OauthIssuerEndpoint); err != nil {
return trace.BadParameter("oauth_issuer endpoint must be a valid URL")
}

if c.ApiEndpoint == "" {
return trace.BadParameter("api_endpoint must be set")
}
if _, err := url.Parse(c.ApiEndpoint); err != nil {
return trace.BadParameter("api_endpoint endpoint must be a valid URL")
}

return nil
}
75 changes: 75 additions & 0 deletions api/types/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1343,3 +1343,78 @@ func TestPluginEmailSettings(t *testing.T) {
})
}
}

func TestNetIQPluginSettings(t *testing.T) {

validSettings := func() *PluginSpecV1_NetIq {
return &PluginSpecV1_NetIq{
NetIq: &PluginNetIQSettings{
OauthIssuerEndpoint: "https://example.com",
ApiEndpoint: "https://example.com",
},
}
}
testCases := []struct {
name string
mutateSettings func(*PluginSpecV1_NetIq)
assertErr require.ErrorAssertionFunc
}{
{
name: "valid",
mutateSettings: nil,
assertErr: require.NoError,
},
{
name: "missing OauthIssuer",
mutateSettings: func(s *PluginSpecV1_NetIq) {
s.NetIq.OauthIssuerEndpoint = ""
},
assertErr: requireNamedBadParameterError("oauth_issuer"),
},
{
name: "missing ApiEndpoint",
mutateSettings: func(s *PluginSpecV1_NetIq) {
s.NetIq.ApiEndpoint = ""
},
assertErr: requireNamedBadParameterError("api_endpoint"),
},
{
name: "incorrect OauthIssuer",
mutateSettings: func(s *PluginSpecV1_NetIq) {
s.NetIq.OauthIssuerEndpoint = "invalidURL%%#"
},
assertErr: requireNamedBadParameterError("oauth_issuer"),
},
{
name: "missing ApiEndpoint",
mutateSettings: func(s *PluginSpecV1_NetIq) {
s.NetIq.ApiEndpoint = "invalidURL%%#"
},
assertErr: requireNamedBadParameterError("api_endpoint"),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
settings := validSettings()
if tc.mutateSettings != nil {
tc.mutateSettings(settings)
}

plugin := NewPluginV1(
Metadata{Name: "uut"},
PluginSpecV1{Settings: settings},
&PluginCredentialsV1{
Credentials: &PluginCredentialsV1_StaticCredentialsRef{
StaticCredentialsRef: &PluginStaticCredentialsRef{
Labels: map[string]string{
"label1": "value1",
},
},
},
})
tc.assertErr(t, plugin.CheckAndSetDefaults())
})
}

}
Loading
Loading