Skip to content

Commit 7380a41

Browse files
authored
Add Federations, Federation Certificates (#8)
1 parent cd9d6a0 commit 7380a41

File tree

17 files changed

+1568
-45
lines changed

17 files changed

+1568
-45
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ You can use this library to work with the following objects of the Selectel IAM
2323
* [serviceusers](https://pkg.go.dev/github.com/selectel/iam-go/service/serviceusers)
2424
* [groups](https://pkg.go.dev/github.com/selectel/iam-go/service/groups)
2525
* [s3credentials](https://pkg.go.dev/github.com/selectel/iam-go/service/s3credentials)
26+
* [federations (saml)](https://pkg.go.dev/github.com/selectel/iam-go/service/federations/saml)
2627

2728
### Installation
2829

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Create Federation with Certificate & add User
2+
3+
This example program demonstrates how to manage creating and deleting Federation with Certificate and assigning Users.
4+
5+
The part of deleting is disabled by `deleteAfterRun` variable.
6+
7+
## Running this example
8+
9+
Running this file will execute the following operations:
10+
11+
1. **Create Federation:** Create is used to create a new Federation.
12+
2. **Create Certificate for Federation:** Create is used to create a new Certificate for Federation.
13+
3. **Create federated User:** Create is used to create a new federated User.
14+
4. **Update Federation:** Updates the Federation Name and Description.
15+
5. **(Delete Federation):** _(disabled by default)_ Delete a just-created Federation on a previous step.
16+
17+
You should see an output like the following:
18+
```
19+
Step 1: Created Federation Name: federation_name ID: 1a2b3c...
20+
Step 2: Created Certificate for Federation ID: 12345_3... Federation ID: 1a2b3c...
21+
Step 3: Created federated User ID: 54321_2... Keystone ID: 1c2b3a...
22+
Step 4: Updated Federation Name and Description
23+
Step 5: Deleting Federation with ID: 1a2b3c...
24+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICmzCCAYMCBgGI6ANFczANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjMwNjIzMTEyNjQ4WhcNMzMwNjIzMTEyODI4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC04rOaDpre/MucE3HXVCnAnpqIqQOeMn696AW2FATnI26x1BsxVAGjcrheAOIu+CxC28m48Ah4+SiTEk/u2X/WbGTd/1GZooz37cge0AWMQGyh8ysZRd6q06kg4QGD1iUtdQyHioMbSr9pPne2QQgSX5/gM9XDuA6dpG9Yv0PIPLFlk3BIUL1qEfUiYbDlrunkN/y4XromJaJPpgXKWraH194bqcgXGQLrCqicKwsRBoQJHg3ODWHjHFOwYODJ1XBsRcAue4J88PKiPV1tZNPVczMptrkqGBYTgOYGjKXGe5EH50RJE4/3Ynurz2s34DSDVJhJOYtGwpfeSuU3i3mVAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGAweCuWJmJXMUdRtgoFIiu6BGotDX5sA/VOm4CRsEXV7/qnBagrAPkRz86KGm4lOPL0X+I13JQh4/OB1gxnPN+BXhNtCWCoj1wA3/BWjs1ow/gaVXzwdy+1mbc/sUBudsLq2Yqs54GgeYsTBKMVpSLKiRg1NebEFlqFmG2hjPzYg1QHL4VBusMQgqt7TTnOfGtdT3Ss9TKGRQ+iwfNL0BtSAKaTRdhNVU4lDYUs788Kw5od/uJj0wTICKO5/PrkX7Uy42+fyU+4SvJynPOy+M+z+s08JC9+eYXixfeeFG1nNWR+DIKXcXaSwNQW+8RweGbOJxQ2BoUKtl0NCHrvxJw=
3+
-----END CERTIFICATE-----
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
"github.com/selectel/iam-go"
9+
"github.com/selectel/iam-go/service/federations/saml"
10+
"github.com/selectel/iam-go/service/federations/saml/certificates"
11+
"github.com/selectel/iam-go/service/roles"
12+
"github.com/selectel/iam-go/service/users"
13+
)
14+
15+
var (
16+
// KeystoneToken
17+
token = "gAAAAA..."
18+
deleteAfterRun = false
19+
20+
// Prefix to be added to User-Agent.
21+
prefix = "iam-go"
22+
23+
federationName = "federation_name"
24+
federationDescription = "federation_description"
25+
updatedFederationName = "new_federation_name"
26+
updatedFederationDescription = "new_federation_description"
27+
28+
certificateName = "certificate name"
29+
certificateDescription = "certificate description"
30+
certificateFileName = "cert.crt"
31+
32+
userEmail = "testmail@example.com"
33+
userExternalID = "some_id"
34+
)
35+
36+
func main() {
37+
// Create a new IAM client.
38+
iamClient, err := iam.New(
39+
iam.WithAuthOpts(&iam.AuthOpts{KeystoneToken: token}),
40+
iam.WithUserAgentPrefix(prefix),
41+
)
42+
if err != nil {
43+
fmt.Println(err)
44+
return
45+
}
46+
47+
federationsAPI := iamClient.SAMLFederations
48+
federationsCertificatesAPI := federationsAPI.Certificates
49+
usersAPI := iamClient.Users
50+
51+
ctx := context.Background()
52+
53+
federation, err := federationsAPI.Create(ctx, saml.CreateRequest{
54+
Name: federationName,
55+
Description: federationDescription,
56+
Issuer: "http://localhost:8080/realms/master",
57+
SSOUrl: "http://localhost:8080/realms/master/protocol/saml",
58+
SessionMaxAgeHours: 24,
59+
SignAuthnRequests: true,
60+
})
61+
if err != nil {
62+
fmt.Println(err)
63+
return
64+
}
65+
fmt.Printf("Step 1: Created Federation Name: %s ID: %s\n", federation.Name, federation.ID)
66+
67+
cert, err := os.ReadFile(certificateFileName)
68+
if err != nil {
69+
fmt.Println(err)
70+
return
71+
}
72+
73+
certificate, err := federationsCertificatesAPI.Create(ctx, federation.ID, certificates.CreateRequest{
74+
Name: certificateName,
75+
Description: certificateDescription,
76+
Data: string(cert),
77+
})
78+
if err != nil {
79+
fmt.Println(err)
80+
return
81+
}
82+
fmt.Printf("Step 2: Created Certificate for Federation ID: %s Federation ID: %s\n", certificate.ID, federation.ID)
83+
84+
user, err := usersAPI.Create(ctx, users.CreateRequest{
85+
AuthType: users.Federated,
86+
Email: userEmail,
87+
Federation: &users.Federation{
88+
ExternalID: userExternalID,
89+
ID: federation.ID,
90+
},
91+
Roles: []roles.Role{{Scope: roles.Account, RoleName: roles.Reader}},
92+
})
93+
if err != nil {
94+
fmt.Println(err)
95+
return
96+
}
97+
fmt.Printf("Step 3: Created federated User ID: %s Keystone ID: %s\n", user.ID, user.KeystoneID)
98+
99+
err = federationsAPI.Update(ctx, federation.ID, saml.UpdateRequest{
100+
Name: updatedFederationName,
101+
Description: &updatedFederationDescription,
102+
})
103+
if err != nil {
104+
fmt.Println(err)
105+
return
106+
}
107+
fmt.Println("Step 4: Updated Federation Name and Description")
108+
109+
if deleteAfterRun {
110+
// Removing User and Federation Certificate is unnecessary because removal of Federation
111+
// also deletes its Certificate and all attached Users
112+
fmt.Printf("Step 5: Deleting Federation with ID: %s\n", federation.ID)
113+
if err = federationsAPI.Delete(ctx, federation.ID); err != nil {
114+
fmt.Println(err)
115+
}
116+
}
117+
}

iam.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/selectel/iam-go/iamerrors"
99
baseclient "github.com/selectel/iam-go/internal/client"
10+
"github.com/selectel/iam-go/service/federations/saml"
1011
"github.com/selectel/iam-go/service/groups"
1112
"github.com/selectel/iam-go/service/s3credentials"
1213
"github.com/selectel/iam-go/service/serviceusers"
@@ -57,6 +58,10 @@ type Client struct {
5758

5859
// S3Credentials instance is used to make requests against Selectel IAM API and manage S3 Credentials.
5960
S3Credentials *s3credentials.Service
61+
62+
// SAMLFederations instance is used to make requests against Selectel IAM API and manage SAML Federations.
63+
// It also contains Certificates service, which is used to manage certificates.
64+
SAMLFederations *saml.Service
6065
}
6166

6267
type AuthOpts struct {
@@ -128,6 +133,7 @@ func New(opts ...Option) (*Client, error) {
128133
c.ServiceUsers = serviceusers.New(c.baseClient)
129134
c.Groups = groups.New(c.baseClient)
130135
c.S3Credentials = s3credentials.New(c.baseClient)
136+
c.SAMLFederations = saml.New(c.baseClient)
131137

132138
return c, nil
133139
}

iam_test.go

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/selectel/iam-go/iamerrors"
1212
baseclient "github.com/selectel/iam-go/internal/client"
13+
"github.com/selectel/iam-go/service/federations/saml"
1314
"github.com/selectel/iam-go/service/groups"
1415
"github.com/selectel/iam-go/service/s3credentials"
1516
"github.com/selectel/iam-go/service/serviceusers"
@@ -57,11 +58,12 @@ func TestNew(t *testing.T) {
5758
authOpts: &AuthOpts{
5859
KeystoneToken: testToken,
5960
},
60-
baseClient: baseClient,
61-
Users: users.New(baseClient),
62-
ServiceUsers: serviceusers.New(baseClient),
63-
Groups: groups.New(baseClient),
64-
S3Credentials: s3credentials.New(baseClient),
61+
baseClient: baseClient,
62+
Users: users.New(baseClient),
63+
ServiceUsers: serviceusers.New(baseClient),
64+
Groups: groups.New(baseClient),
65+
S3Credentials: s3credentials.New(baseClient),
66+
SAMLFederations: saml.New(baseClient),
6567
}
6668
},
6769
expectedError: nil,
@@ -99,11 +101,12 @@ func TestNew(t *testing.T) {
99101
authOpts: &AuthOpts{
100102
KeystoneToken: testToken,
101103
},
102-
baseClient: baseClient,
103-
Users: users.New(baseClient),
104-
ServiceUsers: serviceusers.New(baseClient),
105-
Groups: groups.New(baseClient),
106-
S3Credentials: s3credentials.New(baseClient),
104+
baseClient: baseClient,
105+
Users: users.New(baseClient),
106+
ServiceUsers: serviceusers.New(baseClient),
107+
Groups: groups.New(baseClient),
108+
S3Credentials: s3credentials.New(baseClient),
109+
SAMLFederations: saml.New(baseClient),
107110
}
108111
},
109112
expectedError: nil,
@@ -134,11 +137,12 @@ func TestNew(t *testing.T) {
134137
authOpts: &AuthOpts{
135138
KeystoneToken: testToken,
136139
},
137-
baseClient: baseClient,
138-
Users: users.New(baseClient),
139-
ServiceUsers: serviceusers.New(baseClient),
140-
Groups: groups.New(baseClient),
141-
S3Credentials: s3credentials.New(baseClient),
140+
baseClient: baseClient,
141+
Users: users.New(baseClient),
142+
ServiceUsers: serviceusers.New(baseClient),
143+
Groups: groups.New(baseClient),
144+
S3Credentials: s3credentials.New(baseClient),
145+
SAMLFederations: saml.New(baseClient),
142146
}
143147
},
144148
expectedError: nil,

iamerrors/iamerrors.go

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ var (
3030
ErrGroupNotFound = errors.New("GROUP_NOT_FOUND")
3131
ErrUserOrGroupNotFound = errors.New("USER_OR_GROUP_NOT_FOUND")
3232

33+
ErrFederationNameRequired = errors.New("FEDERATION_NAME_REQUIRED")
34+
ErrFederationIDRequired = errors.New("FEDERATION_ID_REQUIRED")
35+
ErrFederationIssuerRequired = errors.New("FEDERATION_ISSUER_REQUIRED")
36+
ErrFederationSSOURLRequired = errors.New("FEDERATION_SSO_URL_REQUIRED")
37+
ErrFederationCertificateIDRequired = errors.New("FEDERATION_CERTIFICATE_ID_REQUIRED")
38+
ErrFederationMaxAgeHoursRequired = errors.New("FEDERATION_MAX_AGE_HOURS_REQUIRED")
39+
ErrFederationNotFound = errors.New("FEDERATION_NOT_FOUND")
40+
3341
ErrCredentialNameRequired = errors.New("CREDENTIAL_NAME_REQUIRED")
3442
ErrCredentialAccessKeyRequired = errors.New("CREDENTIAL_ACCESS_KEY_REQUIRED")
3543

@@ -48,36 +56,43 @@ var (
4856

4957
//nolint:gochecknoglobals // stringToError is not global.
5058
stringToError = map[string]error{
51-
ErrUserNotFound.Error(): ErrUserNotFound,
52-
ErrClientNoAuthOpts.Error(): ErrClientNoAuthOpts,
53-
ErrAuthTokenUnathorized.Error(): ErrAuthTokenUnathorized,
54-
ErrDomainNotFound.Error(): ErrDomainNotFound,
55-
ErrCredentialNotFound.Error(): ErrCredentialNotFound,
56-
ErrProjectNotFound.Error(): ErrProjectNotFound,
57-
ErrUserAlreadyExists.Error(): ErrUserAlreadyExists,
58-
ErrRequestValidationError.Error(): ErrRequestValidationError,
59-
ErrForbidden.Error(): ErrForbidden,
60-
ErrUnauthorized.Error(): ErrUnauthorized,
61-
ErrInternalServerError.Error(): ErrInternalServerError,
62-
ErrCredentialNameRequired.Error(): ErrCredentialNameRequired,
63-
ErrCredentialAccessKeyRequired.Error(): ErrCredentialAccessKeyRequired,
64-
ErrUserIDRequired.Error(): ErrUserIDRequired,
65-
ErrProjectIDRequired.Error(): ErrProjectIDRequired,
66-
ErrGroupIDRequired.Error(): ErrGroupIDRequired,
67-
ErrGroupUserIDsRequired.Error(): ErrGroupUserIDsRequired,
68-
ErrGroupNameRequired.Error(): ErrGroupNameRequired,
69-
ErrGroupRolesRequired.Error(): ErrGroupRolesRequired,
70-
ErrGroupAlreadyExists.Error(): ErrGroupAlreadyExists,
71-
ErrGroupNotFound.Error(): ErrGroupNotFound,
72-
ErrUserOrGroupNotFound.Error(): ErrUserOrGroupNotFound,
73-
ErrServiceUserNameRequired.Error(): ErrServiceUserNameRequired,
74-
ErrServiceUserPasswordRequired.Error(): ErrServiceUserPasswordRequired,
75-
ErrServiceUserRolesRequired.Error(): ErrServiceUserRolesRequired,
76-
ErrUserRolesRequired.Error(): ErrUserRolesRequired,
77-
ErrUserEmailRequired.Error(): ErrUserEmailRequired,
78-
ErrInputDataRequired.Error(): ErrInputDataRequired,
79-
ErrInternalAppError.Error(): ErrInternalAppError,
80-
ErrUnknown.Error(): ErrUnknown,
59+
ErrUserNotFound.Error(): ErrUserNotFound,
60+
ErrClientNoAuthOpts.Error(): ErrClientNoAuthOpts,
61+
ErrAuthTokenUnathorized.Error(): ErrAuthTokenUnathorized,
62+
ErrDomainNotFound.Error(): ErrDomainNotFound,
63+
ErrCredentialNotFound.Error(): ErrCredentialNotFound,
64+
ErrProjectNotFound.Error(): ErrProjectNotFound,
65+
ErrUserAlreadyExists.Error(): ErrUserAlreadyExists,
66+
ErrRequestValidationError.Error(): ErrRequestValidationError,
67+
ErrForbidden.Error(): ErrForbidden,
68+
ErrUnauthorized.Error(): ErrUnauthorized,
69+
ErrInternalServerError.Error(): ErrInternalServerError,
70+
ErrCredentialNameRequired.Error(): ErrCredentialNameRequired,
71+
ErrCredentialAccessKeyRequired.Error(): ErrCredentialAccessKeyRequired,
72+
ErrUserIDRequired.Error(): ErrUserIDRequired,
73+
ErrProjectIDRequired.Error(): ErrProjectIDRequired,
74+
ErrGroupIDRequired.Error(): ErrGroupIDRequired,
75+
ErrGroupUserIDsRequired.Error(): ErrGroupUserIDsRequired,
76+
ErrGroupNameRequired.Error(): ErrGroupNameRequired,
77+
ErrGroupRolesRequired.Error(): ErrGroupRolesRequired,
78+
ErrGroupAlreadyExists.Error(): ErrGroupAlreadyExists,
79+
ErrGroupNotFound.Error(): ErrGroupNotFound,
80+
ErrFederationNameRequired.Error(): ErrFederationNameRequired,
81+
ErrFederationIDRequired.Error(): ErrFederationIDRequired,
82+
ErrFederationIssuerRequired.Error(): ErrFederationIssuerRequired,
83+
ErrFederationSSOURLRequired.Error(): ErrFederationSSOURLRequired,
84+
ErrFederationCertificateIDRequired.Error(): ErrFederationCertificateIDRequired,
85+
ErrFederationNotFound.Error(): ErrFederationNotFound,
86+
ErrFederationMaxAgeHoursRequired.Error(): ErrFederationMaxAgeHoursRequired,
87+
ErrUserOrGroupNotFound.Error(): ErrUserOrGroupNotFound,
88+
ErrServiceUserNameRequired.Error(): ErrServiceUserNameRequired,
89+
ErrServiceUserPasswordRequired.Error(): ErrServiceUserPasswordRequired,
90+
ErrServiceUserRolesRequired.Error(): ErrServiceUserRolesRequired,
91+
ErrUserRolesRequired.Error(): ErrUserRolesRequired,
92+
ErrUserEmailRequired.Error(): ErrUserEmailRequired,
93+
ErrInputDataRequired.Error(): ErrInputDataRequired,
94+
ErrInternalAppError.Error(): ErrInternalAppError,
95+
ErrUnknown.Error(): ErrUnknown,
8196
}
8297
)
8398

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package certificates provides a set of functions for interacting with the Selectel Federations Certificates API.
2+
package certificates

0 commit comments

Comments
 (0)