diff --git a/.gitignore b/.gitignore index e43b0f9..4befed3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +.idea diff --git a/header-creation/maverics.yaml b/header-creation/maverics.yaml index 3d1139d..be8554d 100644 --- a/header-creation/maverics.yaml +++ b/header-creation/maverics.yaml @@ -12,7 +12,7 @@ http: appgateways: - name: exampleHeaderCreation - location: / + basePath: / # The 'upstream' used here is purely for demonstration and can be replaced with # any URL that is resolvable from the machine the Orchestrator is running on. upstream: https://httpbin.org @@ -20,16 +20,16 @@ appgateways: headers: EXAMPLE-SUB: azure.sub # It is assumed the header.go Service Extension file resides in the - # '/etc/maverics' directory. To change that, update the 'file' fields below. + # '/etc/maverics/extensions' directory. To change that, update the 'file' fields below. # TODO: confirm the file paths defined below are correct. EXAMPLE-FIRST-NAME: createHeader: funcName: CreateFirstNameHeader - file: /etc/maverics/header.go + file: /etc/maverics/extensions/header.go EXAMPLE-LAST-NAME: createHeader: funcName: CreateLastNameHeader - file: /etc/maverics/header.go + file: /etc/maverics/extensions/header.go policies: - location: / @@ -39,13 +39,10 @@ appgateways: authorization: allowAll: true -# Azure AD is used as the IDP in this example, but that can easily be changed by -# modifying the IDP and connector definitions below. For more info on connectors, -# please reference https://docs.strata.io/get-acquainted/connectors. -idps: - - name: azure - connectors: + # Azure AD is used as the IDP in this example, but that can easily be changed by + # modifying the IDP and connector definitions below. For more info on connectors, + # please reference https://docs.strata.io/get-acquainted/connectors. - name: azure type: azure authType: oidc diff --git a/idp-selector/maverics.yaml b/idp-selector/maverics.yaml index e09d784..4db551d 100644 --- a/idp-selector/maverics.yaml +++ b/idp-selector/maverics.yaml @@ -12,7 +12,7 @@ http: appgateways: - name: headers - location: / + basePath: / # The 'upstream' used here is purely for demonstration and can be replaced with # any URL that is resolvable from the machine the Orchestrator is running on. upstream: https://httpbin.org @@ -21,26 +21,22 @@ appgateways: - location: / authentication: # It is assumed the auth.go Service Extension file resides in the - # '/etc/maverics' directory. To change that, update the 'file' fields below. + # '/etc/maverics/extensions' directory. To change that, update the 'file' fields below. isAuthenticatedSE: funcName: IsAuthenticated - file: /etc/maverics/auth.go + file: /etc/maverics/extensions/auth.go authenticateSE: funcName: Authenticate - file: /etc/maverics/auth.go + file: /etc/maverics/extensions/auth.go authorization: allowAll: true - - location: ~ \.(jpg|png|ico|svg|ttf|js|css) + - location: ~ \.(jpg|png|ico|svg|ttf|js|css)$ authentication: allowUnauthenticated: true authorization: allowAll: true -idps: - - name: azure - - name: auth0 - connectors: # The 'name' property is used as a unique ID that the Service Extensions depend on. # Please ensure the Service Extensions are updated if the name of the connectors change. diff --git a/ldap-search/README.md b/ldap-search/README.md new file mode 100644 index 0000000..7548e21 --- /dev/null +++ b/ldap-search/README.md @@ -0,0 +1,33 @@ +# LDAP Search Service Extension + +There are times when you may find the need to construct a unique LDAP search +query that isn't easily available via the LDAP connector. For example, +returning multiple result entries. + +This example will show you how to query LDAP securely by upgrading the TCP +connection to TLS and then make a request to the LDAP server to retrieve groups +with a specific `uniqueMember` attribute. + +The structure of the LDAP can be seen in the [`example.ldif`](./example.ldif) file. + +For more information regarding the `maverics/ldap` pkg, please refer to +the [Service Extension Maverics package documentation][maverics-ldap-docs]. + +## Setup + +Please reference the [maverics.yaml](maverics.yaml) configuration file and the +Service Extension files it references for a set action items specified with`TODO`. +These action items are changes necessary to get this example running. + +## Testing + +1. Complete all the action items specified by `TODO`s +1. Restart the Orchestrator, and ensure it starts successfully. +1. Navigate to the URL the Orchestrator is listening on in your browser: + e.g. https://localhost/headers. +1. You should now be redirected your specified IDP and prompted for authentication. +1. After successfully logging in, the custom headers that were created will be sent + to the upstream application. This can be confirmed by verifying the + `EXAMPLE-GROUPS` header is rendered on the `/headers` page of the sample app. + +[maverics-ldap-docs]: https://docs.strata.io/orchestrator-reference/service-extensions/maverics-packages#package-maverics-ldap \ No newline at end of file diff --git a/ldap-search/example.ldif b/ldap-search/example.ldif new file mode 100644 index 0000000..ad3ffb6 --- /dev/null +++ b/ldap-search/example.ldif @@ -0,0 +1,75 @@ +version: 1 + +dn: dc=examples,dc=com +objectClass: dcObject +objectClass: organization +objectClass: top +dc: examples +o: Example Org + +dn: ou=People,dc=examples,dc=com +objectClass: organizationalUnit +ou: People + +dn: uid=state,ou=People,dc=examples,dc=com +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Samuel Tate +sn: Tate +givenName: Samuel +mail: state@examples.com +mobile: 604-555-5555 +uid: state +userPassword:: d9TEVvbr3a#LeUc$p + +dn: uid=aadkins,ou=People,dc=examples,dc=com +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Allan Adkins +sn: Adkins +givenName: Allan +mail: aadkins@examples.com +mobile: 604-555-9090 +uid: aadkins +userPassword:: d9TEVvbr3a#LeUc$p + +dn: uid=shiggins,ou=People,dc=examples,dc=com +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: Shelly Higgins +sn: Higgins +givenName: Shelly +mail: shiggins@examples.com +mobile: 415-123-4567 +uid: shiggins +userPassword:: d9TEVvbr3a#LeUc$p + +dn: ou=groups,dc=examples,dc=com +objectClass: organizationalUnit +objectClass: top +ou: groups + +dn: cn=GroupOne,ou=groups,dc=examples,dc=com +objectClass: groupOfUniqueNames +objectClass: top +cn: GroupOne +uniqueMember: uid=state@examples.com,ou=People,dc=examples,dc=com + +dn: cn=GroupTwo,ou=groups,dc=examples,dc=com +objectClass: groupOfUniqueNames +objectClass: top +cn: GroupTwo +uniqueMember: uid=state@examples.com,ou=People,dc=examples,dc=com + +dn: cn=GroupThree,ou=groups,dc=examples,dc=com +objectClass: groupOfUniqueNames +objectClass: top +cn: GroupThree +uniqueMember: uid=aadkins@examples.com,ou=People,dc=examples,dc=com + diff --git a/ldap-search/loadAttrs.go b/ldap-search/loadAttrs.go new file mode 100644 index 0000000..ee79ef0 --- /dev/null +++ b/ldap-search/loadAttrs.go @@ -0,0 +1,114 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net/http" + "strings" + + "maverics/app" + "maverics/ldap" + "maverics/log" + "maverics/secret" + "maverics/session" +) + +const ( + // TODO: Adjust these values based on your LDAP configuration. + ldapServerName = "ldap.examples.com" + ldapBaseDN = "dc=examples,dc=com" + ldapFilterFmt = "(&(uniquemember=uid=%s,ou=People,dc=examples,dc=com))" + + delimiter = "," +) + +// LoadAttrs loads attributes from LDAP and then stores them on the session for later +// use. +func LoadAttrs(_ *app.AppGateway, _ http.ResponseWriter, req *http.Request) error { + log.Debug("se", "loading attributes from LDAP") + + uid := session.GetString(req, "azure.email") + if uid == "" { + return fmt.Errorf("unable to get uid from session") + } + filter := fmt.Sprintf(ldapFilterFmt, uid) + groupsMap, err := getGroups(ldapBaseDN, filter) + if err != nil { + log.Error("se", "unable to get groups", "error", err.Error()) + return err + } + + groups := make([]string, 0, len(groupsMap)) + for k, _ := range groupsMap { + groups = append(groups, k) + } + + list := strings.Join(groups, delimiter) + log.Debug( + "se", "setting groups attribute on session", + "se.groups", list, + ) + + session.Set(req, "se.groups", list) + + return nil +} + +// getGroups will search the LDAP ldapBaseDN using the provided filter and return a +// list of unique groups. +func getGroups(baseDN, filter string) (map[string]struct{}, error) { + ldapURL := fmt.Sprintf("ldap://%s", ldapServerName) + log.Info("se", "dialing to ldap over tcp", "url", ldapURL) + conn, err := ldap.DialURL(ldapURL) + if err != nil { + return nil, fmt.Errorf("unable to dial ldap: %w", err) + } + defer conn.Close() + + caCert := secret.GetString("ldapCACert") + certPool, err := x509.SystemCertPool() + if err != nil { + return nil, fmt.Errorf("unable to get system cert pool: %w", err) + } + ok := certPool.AppendCertsFromPEM([]byte(caCert)) + if !ok { + return nil, errors.New("unable to append ca cert to pool") + } + err = conn.StartTLS(&tls.Config{ + RootCAs: certPool, + ServerName: ldapServerName, + }) + if err != nil { + return nil, fmt.Errorf("unable to start tls: %w", err) + } + + serviceAccountUsername := secret.GetString("serviceAccountUsername") + serviceAccountPassword := secret.GetString("serviceAccountPassword") + err = conn.Bind(serviceAccountUsername, serviceAccountPassword) + if err != nil { + return nil, fmt.Errorf("unable to bind ldap: %w", err) + } + + searchReq := ldap.NewSearchRequest( + baseDN, // The base dn to search + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + filter, // The filter to apply + []string{"cn"}, // A list attributes to retrieve + nil, + ) + searchResult, err := conn.Search(searchReq) + if err != nil { + return nil, fmt.Errorf("unable to search ldap: %w", err) + } + + groups := make(map[string]struct{}) + for _, entry := range searchResult.Entries { + groups[entry.GetAttributeValue("cn")] = struct{}{} + + fmt.Printf("%s: %v\n", entry.DN, entry.GetAttributeValue("cn")) + } + + return groups, nil +} diff --git a/ldap-search/maverics.yaml b/ldap-search/maverics.yaml new file mode 100644 index 0000000..473e605 --- /dev/null +++ b/ldap-search/maverics.yaml @@ -0,0 +1,57 @@ +version: ldap-search-loadAttrs + +tls: + maverics: + # TODO: replace the 'certFile' and 'keyFile' values with an absolute path to a + # certificate pair. For more info on the TLS configuration, please reference + # https://scriptum.strata.io/get-started/transport-security. + certFile: { ABSOLUTE PATH TO CERT FILE } + keyFile: { ABSOLUTE PATH TO KEY FILE } + +http: + address: ":443" + tls: maverics + +logger: + level: debug + +appgateways: + - name: exampleLDAPSearchLoadAttrs + basePath: / + # The 'upstream' used here is purely for demonstration and can be replaced with + # any URL that is resolvable from the machine the Orchestrator is running on. + upstream: https://cylog.org + headers: + EXAMPLE-GROUPS: se.groups + + loadAttrsSE: + # It is assumed the loadAttrs.go Service Extension file resides in the + # '/etc/maverics/extensions' directory. To change that, update the 'file' field below. + funcName: LoadAttrs + file: /etc/maverics/extensions/loadAttrs.go + + policies: + - location: ~ \.(jpg|png|ico|svg|ttf|js|css|gif)$ + authentication: + allowUnauthenticated: true + authorization: + allowAll: true + - location: / + authentication: + idps: + - azure + authorization: + allowAll: true + +connectors: + # The 'name' property is used as a unique ID that the Service Extensions depend on. + # Please ensure the Service Extensions are updated if the name of the connectors change. + # Additionally, please note that the 'oauthRedirectURL' used in the two connectors + # must be unique. + - name: azure + type: azure + authType: saml + # TODO: Configure the SAML Azure IDP. + samlMetadataURL: { SAML METADATA URL } + samlConsumerServiceURL: { SAML CONSUMER SERVICE URL } + samlEntityID: { SAML ENTITY ID } \ No newline at end of file diff --git a/ldap-search/secrets.yaml b/ldap-search/secrets.yaml new file mode 100644 index 0000000..2b2f261 --- /dev/null +++ b/ldap-search/secrets.yaml @@ -0,0 +1,8 @@ +secrets: + serviceAccountUsername: cn=service,dc=examples,dc=com + serviceAccountPassword: uCgfpeeyGk9Vgf$*$ + ldapCACert: |+ + -----BEGIN CERTIFICATE----- + TODO: Add LDAP CA Cert. + -----END CERTIFICATE----- +