Skip to content

Commit 086786c

Browse files
temblekingfcracker79
authored andcommitted
feat(sso): add sysdig_sso_openid and sysdig_sso_saml resources (#688)
## Summary - Add `sysdig_sso_openid` resource for OpenID Connect SSO configuration - Add `sysdig_sso_saml` resource for SAML SSO configuration Both resources use the `/platform/v1/sso-settings/` endpoint with different `type` discriminators. ### sysdig_sso_openid features: - Issuer URL with optional metadata discovery - Client ID/Secret authentication - Manual metadata configuration when discovery is disabled - Additional OAuth scopes support ### sysdig_sso_saml features: - Metadata via URL or inline XML (mutually exclusive) - Email parameter mapping - Security settings (signature validation, signed assertions, destination verification, encryption) ### Common SSO features (both resources): - Product selection (monitor/secure) - Group mapping configuration - Single logout support - Auto user creation on login - Optimistic locking via version field ## Test plan - [ ] Run acceptance tests for OpenID: `go test ./sysdig -v -run TestAccSSOOpenID -tags=tf_acc_sysdig_secure -timeout 120m` - [ ] Run acceptance tests for SAML: `go test ./sysdig -v -run TestAccSSOSaml -tags=tf_acc_sysdig_secure -timeout 120m` - [ ] Verify import functionality for both resources - [ ] Test update operations with version-based optimistic locking
1 parent 2b08a74 commit 086786c

12 files changed

Lines changed: 1914 additions & 1 deletion

sysdig/internal/client/v2/model.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,3 +1201,80 @@ type ZoneScope struct {
12011201
TargetType string `json:"targetType"`
12021202
Rules string `json:"rules"`
12031203
}
1204+
1205+
// SSO OpenID Connect configuration - combines base SSO fields with OpenID-specific config
1206+
type SSOOpenID struct {
1207+
// Response-only identification fields
1208+
ID int `json:"id,omitempty"`
1209+
Version int `json:"version,omitempty"`
1210+
DateCreated string `json:"dateCreated,omitempty"`
1211+
LastUpdated string `json:"lastUpdated,omitempty"`
1212+
1213+
// Base SSO fields (root level in API)
1214+
Product string `json:"product,omitempty"`
1215+
IsActive bool `json:"isActive"`
1216+
CreateUserOnLogin bool `json:"createUserOnLogin"`
1217+
IsSingleLogoutEnabled bool `json:"isSingleLogoutEnabled"`
1218+
IsGroupMappingEnabled bool `json:"isGroupMappingEnabled"`
1219+
GroupMappingAttributeName string `json:"groupMappingAttributeName,omitempty"`
1220+
IntegrationName string `json:"integrationName,omitempty"`
1221+
1222+
// OpenID-specific config (nested in "config" in API)
1223+
Config *SSOOpenIDConfig `json:"config,omitempty"`
1224+
}
1225+
1226+
// SSOOpenIDConfig contains OpenID-specific fields that go inside the "config" block
1227+
type SSOOpenIDConfig struct {
1228+
Type string `json:"type"` // "OPENID"
1229+
IssuerURL string `json:"issuerUrl"`
1230+
ClientID string `json:"clientId"`
1231+
ClientSecret string `json:"clientSecret,omitempty"`
1232+
IsMetadataDiscoveryEnabled bool `json:"isMetadataDiscoveryEnabled"`
1233+
Metadata *OpenIDMetadata `json:"metadata,omitempty"`
1234+
GroupAttributeName string `json:"groupAttributeName,omitempty"`
1235+
IsAdditionalScopesCheckEnabled bool `json:"isAdditionalScopesCheckEnabled"`
1236+
AdditionalScopes []string `json:"additionalScopes,omitempty"`
1237+
}
1238+
1239+
type OpenIDMetadata struct {
1240+
Issuer string `json:"issuer"`
1241+
AuthorizationEndpoint string `json:"authorizationEndpoint"`
1242+
TokenEndpoint string `json:"tokenEndpoint"`
1243+
JwksURI string `json:"jwksUri"`
1244+
TokenAuthMethod string `json:"tokenAuthMethod"`
1245+
EndSessionEndpoint string `json:"endSessionEndpoint,omitempty"`
1246+
UserInfoEndpoint string `json:"userInfoEndpoint,omitempty"`
1247+
}
1248+
1249+
// SSO SAML configuration - combines base SSO fields with SAML-specific config
1250+
type SSOSaml struct {
1251+
// Response-only identification fields
1252+
ID int `json:"id,omitempty"`
1253+
Version int `json:"version,omitempty"`
1254+
DateCreated string `json:"dateCreated,omitempty"`
1255+
LastUpdated string `json:"lastUpdated,omitempty"`
1256+
1257+
// Base SSO fields (root level in API)
1258+
Product string `json:"product,omitempty"`
1259+
IsActive bool `json:"isActive"`
1260+
CreateUserOnLogin bool `json:"createUserOnLogin"`
1261+
IsSingleLogoutEnabled bool `json:"isSingleLogoutEnabled"`
1262+
IsGroupMappingEnabled bool `json:"isGroupMappingEnabled"`
1263+
GroupMappingAttributeName string `json:"groupMappingAttributeName,omitempty"`
1264+
IntegrationName string `json:"integrationName,omitempty"`
1265+
1266+
// SAML-specific config (nested in "config" in API)
1267+
Config *SSOSamlConfig `json:"config,omitempty"`
1268+
}
1269+
1270+
// SSOSamlConfig contains SAML-specific fields that go inside the "config" block
1271+
type SSOSamlConfig struct {
1272+
Type string `json:"type"` // "SAML"
1273+
MetadataURL string `json:"metadataUrl,omitempty"`
1274+
MetadataXML string `json:"metadataXml,omitempty"`
1275+
EmailParameter string `json:"emailParameter"`
1276+
IsSignatureValidationEnabled *bool `json:"isSignatureValidationEnabled,omitempty"`
1277+
IsSignedAssertionEnabled *bool `json:"isSignedAssertionEnabled,omitempty"`
1278+
IsDestinationVerificationEnabled *bool `json:"isDestinationVerificationEnabled,omitempty"`
1279+
IsEncryptionSupportEnabled *bool `json:"isEncryptionSupportEnabled,omitempty"`
1280+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package v2
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"net/http"
8+
)
9+
10+
var ErrSSOOpenIDNotFound = errors.New("SSO OpenID configuration not found")
11+
12+
const (
13+
createSSOOpenIDPath = "%s/platform/v1/sso-settings/"
14+
getSSOOpenIDPath = "%s/platform/v1/sso-settings/%d"
15+
updateSSOOpenIDPath = "%s/platform/v1/sso-settings/%d"
16+
deleteSSOOpenIDPath = "%s/platform/v1/sso-settings/%d"
17+
)
18+
19+
type SSOOpenIDInterface interface {
20+
Base
21+
CreateSSOOpenID(ctx context.Context, sso *SSOOpenID) (*SSOOpenID, error)
22+
GetSSOOpenID(ctx context.Context, id int) (*SSOOpenID, error)
23+
UpdateSSOOpenID(ctx context.Context, id int, sso *SSOOpenID) (*SSOOpenID, error)
24+
DeleteSSOOpenID(ctx context.Context, id int) error
25+
}
26+
27+
func (c *Client) CreateSSOOpenID(ctx context.Context, sso *SSOOpenID) (result *SSOOpenID, err error) {
28+
payload, err := Marshal(sso)
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
response, err := c.requester.Request(ctx, http.MethodPost, c.createSSOOpenIDURL(), payload)
34+
if err != nil {
35+
return nil, err
36+
}
37+
defer func() {
38+
if dErr := response.Body.Close(); dErr != nil {
39+
err = fmt.Errorf("unable to close response body: %w", dErr)
40+
}
41+
}()
42+
43+
if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
44+
return nil, c.ErrorFromResponse(response)
45+
}
46+
47+
return Unmarshal[*SSOOpenID](response.Body)
48+
}
49+
50+
func (c *Client) GetSSOOpenID(ctx context.Context, id int) (result *SSOOpenID, err error) {
51+
response, err := c.requester.Request(ctx, http.MethodGet, c.getSSOOpenIDURL(id), nil)
52+
if err != nil {
53+
return nil, err
54+
}
55+
defer func() {
56+
if dErr := response.Body.Close(); dErr != nil {
57+
err = fmt.Errorf("unable to close response body: %w", dErr)
58+
}
59+
}()
60+
61+
if response.StatusCode == http.StatusNotFound {
62+
return nil, ErrSSOOpenIDNotFound
63+
}
64+
if response.StatusCode != http.StatusOK {
65+
return nil, c.ErrorFromResponse(response)
66+
}
67+
68+
return Unmarshal[*SSOOpenID](response.Body)
69+
}
70+
71+
func (c *Client) UpdateSSOOpenID(ctx context.Context, id int, sso *SSOOpenID) (result *SSOOpenID, err error) {
72+
payload, err := Marshal(sso)
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
response, err := c.requester.Request(ctx, http.MethodPut, c.updateSSOOpenIDURL(id), payload)
78+
if err != nil {
79+
return nil, err
80+
}
81+
defer func() {
82+
if dErr := response.Body.Close(); dErr != nil {
83+
err = fmt.Errorf("unable to close response body: %w", dErr)
84+
}
85+
}()
86+
87+
if response.StatusCode != http.StatusOK {
88+
return nil, c.ErrorFromResponse(response)
89+
}
90+
91+
return Unmarshal[*SSOOpenID](response.Body)
92+
}
93+
94+
func (c *Client) DeleteSSOOpenID(ctx context.Context, id int) (err error) {
95+
response, err := c.requester.Request(ctx, http.MethodDelete, c.deleteSSOOpenIDURL(id), nil)
96+
if err != nil {
97+
return err
98+
}
99+
defer func() {
100+
if dErr := response.Body.Close(); dErr != nil {
101+
err = fmt.Errorf("unable to close response body: %w", dErr)
102+
}
103+
}()
104+
105+
if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNotFound {
106+
return c.ErrorFromResponse(response)
107+
}
108+
109+
return nil
110+
}
111+
112+
func (c *Client) createSSOOpenIDURL() string {
113+
return fmt.Sprintf(createSSOOpenIDPath, c.config.url)
114+
}
115+
116+
func (c *Client) getSSOOpenIDURL(id int) string {
117+
return fmt.Sprintf(getSSOOpenIDPath, c.config.url, id)
118+
}
119+
120+
func (c *Client) updateSSOOpenIDURL(id int) string {
121+
return fmt.Sprintf(updateSSOOpenIDPath, c.config.url, id)
122+
}
123+
124+
func (c *Client) deleteSSOOpenIDURL(id int) string {
125+
return fmt.Sprintf(deleteSSOOpenIDPath, c.config.url, id)
126+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package v2
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"net/http"
8+
)
9+
10+
var ErrSSOSamlNotFound = errors.New("SSO SAML configuration not found")
11+
12+
const (
13+
createSSOSamlPath = "%s/platform/v1/sso-settings/"
14+
getSSOSamlPath = "%s/platform/v1/sso-settings/%d"
15+
updateSSOSamlPath = "%s/platform/v1/sso-settings/%d"
16+
deleteSSOSamlPath = "%s/platform/v1/sso-settings/%d"
17+
)
18+
19+
type SSOSamlInterface interface {
20+
Base
21+
CreateSSOSaml(ctx context.Context, sso *SSOSaml) (*SSOSaml, error)
22+
GetSSOSaml(ctx context.Context, id int) (*SSOSaml, error)
23+
UpdateSSOSaml(ctx context.Context, id int, sso *SSOSaml) (*SSOSaml, error)
24+
DeleteSSOSaml(ctx context.Context, id int) error
25+
}
26+
27+
func (c *Client) CreateSSOSaml(ctx context.Context, sso *SSOSaml) (result *SSOSaml, err error) {
28+
payload, err := Marshal(sso)
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
response, err := c.requester.Request(ctx, http.MethodPost, c.createSSOSamlURL(), payload)
34+
if err != nil {
35+
return nil, err
36+
}
37+
defer func() {
38+
if dErr := response.Body.Close(); dErr != nil {
39+
err = fmt.Errorf("unable to close response body: %w", dErr)
40+
}
41+
}()
42+
43+
if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated {
44+
return nil, c.ErrorFromResponse(response)
45+
}
46+
47+
return Unmarshal[*SSOSaml](response.Body)
48+
}
49+
50+
func (c *Client) GetSSOSaml(ctx context.Context, id int) (result *SSOSaml, err error) {
51+
response, err := c.requester.Request(ctx, http.MethodGet, c.getSSOSamlURL(id), nil)
52+
if err != nil {
53+
return nil, err
54+
}
55+
defer func() {
56+
if dErr := response.Body.Close(); dErr != nil {
57+
err = fmt.Errorf("unable to close response body: %w", dErr)
58+
}
59+
}()
60+
61+
if response.StatusCode == http.StatusNotFound {
62+
return nil, ErrSSOSamlNotFound
63+
}
64+
if response.StatusCode != http.StatusOK {
65+
return nil, c.ErrorFromResponse(response)
66+
}
67+
68+
return Unmarshal[*SSOSaml](response.Body)
69+
}
70+
71+
func (c *Client) UpdateSSOSaml(ctx context.Context, id int, sso *SSOSaml) (result *SSOSaml, err error) {
72+
payload, err := Marshal(sso)
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
response, err := c.requester.Request(ctx, http.MethodPut, c.updateSSOSamlURL(id), payload)
78+
if err != nil {
79+
return nil, err
80+
}
81+
defer func() {
82+
if dErr := response.Body.Close(); dErr != nil {
83+
err = fmt.Errorf("unable to close response body: %w", dErr)
84+
}
85+
}()
86+
87+
if response.StatusCode != http.StatusOK {
88+
return nil, c.ErrorFromResponse(response)
89+
}
90+
91+
return Unmarshal[*SSOSaml](response.Body)
92+
}
93+
94+
func (c *Client) DeleteSSOSaml(ctx context.Context, id int) (err error) {
95+
response, err := c.requester.Request(ctx, http.MethodDelete, c.deleteSSOSamlURL(id), nil)
96+
if err != nil {
97+
return err
98+
}
99+
defer func() {
100+
if dErr := response.Body.Close(); dErr != nil {
101+
err = fmt.Errorf("unable to close response body: %w", dErr)
102+
}
103+
}()
104+
105+
if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNotFound {
106+
return c.ErrorFromResponse(response)
107+
}
108+
109+
return nil
110+
}
111+
112+
func (c *Client) createSSOSamlURL() string {
113+
return fmt.Sprintf(createSSOSamlPath, c.config.url)
114+
}
115+
116+
func (c *Client) getSSOSamlURL(id int) string {
117+
return fmt.Sprintf(getSSOSamlPath, c.config.url, id)
118+
}
119+
120+
func (c *Client) updateSSOSamlURL(id int) string {
121+
return fmt.Sprintf(updateSSOSamlPath, c.config.url, id)
122+
}
123+
124+
func (c *Client) deleteSSOSamlURL(id int) string {
125+
return fmt.Sprintf(deleteSSOSamlPath, c.config.url, id)
126+
}

sysdig/internal/client/v2/sysdig.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ type SysdigCommon interface {
2525
GroupMappingInterface
2626
IPFilteringSettingsInterface
2727
IPFiltersInterface
28+
SSOOpenIDInterface
29+
SSOSamlInterface
2830
TeamServiceAccountInterface
2931
}
3032

sysdig/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ func (p *SysdigProvider) Provider() *schema.Provider {
121121
"sysdig_group_mapping_config": resourceSysdigGroupMappingConfig(),
122122
"sysdig_ip_filter": resourceSysdigIPFilter(),
123123
"sysdig_ip_filtering_settings": resourceSysdigIPFilteringSettings(),
124+
"sysdig_sso_openid": resourceSysdigSSOOpenID(),
125+
"sysdig_sso_saml": resourceSysdigSSOSaml(),
124126
"sysdig_team_service_account": resourceSysdigTeamServiceAccount(),
125127
"sysdig_user": resourceSysdigUser(),
126128

sysdig/resource_sysdig_secure_cloud_auth_account_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,6 @@ func TestAccAWSSecureCloudAuthAccountResponseActions(t *testing.T) {
574574
},
575575
},
576576
})
577-
578577
}
579578

580579
func TestAccAWSSecureCloudAccountThreatDetection(t *testing.T) {

0 commit comments

Comments
 (0)