From 5953474af63fc7da38589f4b579308478559aef8 Mon Sep 17 00:00:00 2001 From: Warren Fernandes Date: Fri, 23 Jun 2023 11:12:46 -0600 Subject: [PATCH] Update examples (#24) * Update location to basePath appgateway.location property has been deprecated in favor of basePath * Update extensions path to match our docs.strata.io * Update allowUnauthenticated regex The intent of this policy is to allow resources such as favicon.ico to be proxied without authentication required. However, the previous regex also matched resources such as 'resource.jpg.txt'. The updated regex will ensure that the extensions are at the end of the resource path. * Adds .idea to .gitignore * Remove unnecessary idps block * Add LDAP search example (#25) Signed-off-by: Warren Fernandes --- .gitignore | 1 + header-creation/maverics.yaml | 17 +++-- idp-selector/maverics.yaml | 14 ++--- ldap-search/README.md | 33 ++++++++++ ldap-search/example.ldif | 75 ++++++++++++++++++++++ ldap-search/loadAttrs.go | 114 ++++++++++++++++++++++++++++++++++ ldap-search/maverics.yaml | 57 +++++++++++++++++ ldap-search/secrets.yaml | 8 +++ 8 files changed, 300 insertions(+), 19 deletions(-) create mode 100644 ldap-search/README.md create mode 100644 ldap-search/example.ldif create mode 100644 ldap-search/loadAttrs.go create mode 100644 ldap-search/maverics.yaml create mode 100644 ldap-search/secrets.yaml 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----- +