diff --git a/aws/security_plugins/README.md b/aws/security_plugins/README.md new file mode 100644 index 0000000..f860af0 --- /dev/null +++ b/aws/security_plugins/README.md @@ -0,0 +1,18 @@ +***************************************************************************** +* README for AWS Security Plugin + +* Last Update: January 2024 + +* Security plugins, in general can be used to replace or extend the +* mechanisms that DB2 uses to authenticate users and obtain their +* group memberships. This AWS IAM security plugin is designed to authenticate +* AWS Cognito users using ACCESSTOKEN to connect to Db2. + +* For information on developing, building and deploying this security plugin, +* see the [README](db2-aws-iam/README.md). Refer [`AWS_cognito.md`](AWS_cognito.md) +* to know one can setup AWS cognito, create users and groups, and retrieve token to be +* used for Db2 authentication. + +* This plugin is built and tested with Db2 11.5.9 version. +* +***************************************************************************** diff --git a/aws/security_plugins/db2-aws-iam/AWS_cognito.md b/aws/security_plugins/db2-aws-iam/AWS_cognito.md new file mode 100644 index 0000000..8d91a04 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/AWS_cognito.md @@ -0,0 +1,204 @@ +# AWS Cognito User Pool + +This document lists the steps to setup Cognito user pool, users, groups and token retrieval. + +## Create user pool +1. Go to AWS Cognito console +2. Click on “Create user pool” +3. Select “User name” as cognito user pool sign-in options +4. Tick the User name requirements as per need and click on “Next” +5. Select “No MFA” and click on “Next” +6. Uncheck Enable self-registration and click on “Next” +7. Select “Send email with Cognito” as Email provider and click on “Next” +8. Enter any user pool name ex. DB2UserPool +9. Check the “Use the Cognito Hosted UI” option +10. Enter any cognito domain +11. Select App type as Public client +12. Enter any App client name +13. Enter http://localhost as call back URL +14. Select all the authentication flows under Advanced app client settings +15. Select only “Implicit grant” in OAuth 2.0 grant types and click on “Next” +16. Click on “Create user pool” + +## Create a user in user pool +1. Go to AWS Cognito console +2. Click on the user pool name to go to the next page +3. Under “Users” tab, click on “Create user” button +4. Enter the User name +5. Enter password for the User name +6. Click on “Create user" to create the user +7. Run below command to confirm the user, replace with the user pool id and with the new user password + + aws cognito-idp admin-set-user-password --user-pool-id --username --password --permanent + + Ex: +```shell + aws cognito-idp admin-set-user-password --user-pool-id us-east-1_DiDR8M202 --username test1 --password *U7y6t5r --permanent +``` + +8. Verify that user’s confirmation status is “confirmed” in AWS cognito console + + +## Create a group in user pool +1. Go to AWS Cognito console and go to User pools +2. Click on the user pool name to go to the next page +3. Under “Groups” tab, click on “Create group” button +4. Enter group name. This name should be same as that of Db2 group names. +5. Click on “Create group” to create the group + +## Map a user to a group in user pool +1. Go to AWS Cognito console and go to User pools +2. Click on the user pool name to go to the next page +3. Under “Groups” tab, click on group name to which user needs to be mapped +4. Click on “Add user to group” +5. Select the user and click on “Add" + +## Create a lambda function + 1. Go to AWS Lambda console + 2. Click on “Create function” + 3. Enter Function name as “generateToken” + 4. Select Runtime as Python 3.11 or above + 5. Click on “Create function” button + 6. Copy & paste the code from “generateToken.py” file or from below to the lambda code editor +```shell +import boto3 +import base64 +import json +import os + +def lambda_handler(event, context): + headers = event['headers'] + auth_header = headers.get('authorization') + base64_string = auth_header.replace("Basic ", "") + decoded_bytes = base64.b64decode(base64_string) + decoded_string = decoded_bytes.decode('utf-8') + username, password = decoded_string.split(":") + + # Initialize the AWS Cognito Identity Provider client + cognito_client = boto3.client('cognito-idp') + + #client_id = '7lhf17juio0frr2vme38qpnsob' + user_pool_id = os.environ["USER_POOL_ID"] + client_id = os.environ["CLIENT_ID"] + + # Create a dictionary with the authentication parameters + auth_params = { + 'USERNAME': username, + 'PASSWORD': password + # 'SECRET_HASH': 'your-secret-hash' # If you have a secret hash configured + } + + # Perform AdminInitiateAuth to initiate authentication + response = cognito_client.admin_initiate_auth( + UserPoolId=user_pool_id, + ClientId=client_id, + AuthFlow='ADMIN_USER_PASSWORD_AUTH', # Use ADMIN_NO_SRP_AUTH for admin-initiated auth + AuthParameters=auth_params + ) + return response +``` + 7. Click on “Deploy” button to deploy the changes + 8. Click on “Configuration” and under Environment variables, click on “Edit” + 9. Click on “Add environment variable” and below environment variables + + Key: CLIENT_ID, Value: \ + + Key: USER_POOL_ID, Value: \ + + (Replace \ and \ with the actual values) + +10. Click on Save to persist the changes +11. Click on “Configuration” and then click on "Permissions" +12. Click on link under the "Role name" +13. Click on "Add permissions" and select "Create inline policy" +14. Select "JSON" policy editor +15. Copy the below polices and paste into the JSON policy editor by replacing the existing policy. Replace {USERPOOL_ARN) with the user pool arn value +```shell +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "cognito-idp:AdminInitiateAuth" + ], + "Resource": "{USERPOOL_ARN}" + } + ] +} +``` +16. Click on "Next" +17. Enter any policy name. ex. db2-lambda-cognito-policy +18. Click on "Create policy" to create the policy + + ## Create an API gateway + 1. Go to AWS API gateway console + 2. Click on “Create API” + 3. For HTTP API, Click on “Build” button and then click on “Add integration" + 4. Choose “Lambda” from the integrations drop down, Select AWS region and Choose a lambda function from Lambda function drop down + 5. Enter API name as “getToken” and Click on “Next” + 6. Select method as “GET", Enter resource path as “/token” and Click on “Next” + 7. Leave Stage name as $default and leave Auth-deploy as On and Click on “Next” + 8. Click on “Create” to create API gateway + 9. Click on API name under API Gateway -> APIs + 10. Click on “API: {API name}...” on the left menu and copy the invoke URL to a text editor + 11. Append “/token” to the Invoke IRL such as “{InvokeURL}/token” + 12. To get the Cognito user pool JWT tokens, call the API gateway URL as + + curl -u '{CognitoUserName}:{CognitoUserPassword}' “{InvokeURL}/token” + +Ex. + ```shell + curl -u 'test1@test.com:1234567890' https://1yq9tq9ojk.execute-api.us-east-1.amazonaws.com/token + ``` + + # To generate JWT using AWS cognito Hosted UI + 1. Go to AWS Cognito console and then go to User pools + 2. Click on the user pool name to go to the next page + 3. Click on App integration tab + 4. Click on App client name under App clients and analytics + 5. Under Hosted UI section, click on View Hosted UI + 6. Enter Cognito user name and password to login + 7. Extract the access token from the redirected URL +```shell +http://localhost/#access_token=eyJraWQiOiIwVGp4dUdVYVVxd0IyUitzVlZnMno1VWxISkIyZERNazh3UkIxTU14WjlvPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiIzYjI4ZjBhYS0zODc2LTQxNTktODNlOS01ODMzMmMzNzQ1ODIiLCJjb2duaXRvOmdyb3VwcyI6WyJEQjJBRE1JTiJdLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0xLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMV9EaURSOE0yMDIiLCJ2ZXJzaW9uIjoyLCJjbGllbnRfaWQiOiIxbmptcjJ2cDhkaXBiMWhuZTBhMHZydjV0ayIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoicGhvbmUgb3BlbmlkIGVtYWlsIiwiYXV0aF90aW1lIjoxNjk4NDA3NjI3LCJleHAiOjE2OTg0MTEyMjcsImlhdCI6MTY5ODQwNzYyNywianRpIjoiNDIzZmVkYTEtOTExMS00ZjlkLTgyMDUtNTgwZTRmM2MyYjI4IiwidXNlcm5hbWUiOiJ0ZXN0MSJ9.E3Kjmy0UIxWtAucxAtnGxVPJ7sTc2zKleRhoA16uMDst0YjLDM9hFuPyVYgvUmx4-W1SYWjpfcrzDNPd5_XSY6bqBGq1VbuGXcC3JO8ZXP_xdojf_4AjUFgAj-xzYPquzGJ2RHgzN5HM3Adv11lrNPynaug7FnbpNz-9bWcRcUOMzWoRd6vC0lqPe1ZY-sNEw8RL8ytqMcZfQTJg7cYE8-ZYoqJ3Yiq2dnZyBI7tIV1ewXmJOni1aPQJrrXB9PCnHK_1hEYzXiKZf4sPBkqihZV9nGCx-nMuGqVR3dAQYT6x8_c5wi2E64-8UWXYmmMgmLjZBMWRigWhT-dlL03EAg&id_token=eyJraWQiOiJOQ1FrNkxvTmpUN3lEYkVLN1ArUEkyVEhZVHdwNlpNT1FPSm9MU24wcXVFPSIsImFsZyI6IlJTMjU2In0.eyJhdF9oYXNoIjoiaGoyd0pzTEdRdno5UkQ3dG81REdEdyIsInN1YiI6IjNiMjhmMGFhLTM4NzYtNDE1OS04M2U5LTU4MzMyYzM3NDU4MiIsImF1ZCI6IjFuam1yMnZwOGRpcGIxaG5lMGEwdnJ2NXRrIiwiY29nbml0bzpncm91cHMiOlsiREIyQURNSU4iXSwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE2OTg0MDc2MjcsImlzcyI6Imh0dHBzOlwvXC9jb2duaXRvLWlkcC51cy1lYXN0LTEuYW1hem9uYXdzLmNvbVwvdXMtZWFzdC0xX0RpRFI4TTIwMiIsImNvZ25pdG86dXNlcm5hbWUiOiJ0ZXN0MSIsImV4cCI6MTY5ODQxMTIyNywiaWF0IjoxNjk4NDA3NjI3LCJqdGkiOiI1MjYwNjMwOC1lMDIyLTRjMGMtYTc0NS02N2Q5NjIxM2NkNWMifQ.Lz2JLxk7tGnuos8x3_CWQ97Vznh0oSHbaQX-8ZKxXI2JE0fHhruIV69VBWiw5RS8RrUdDsWlu_4Ab02rTWo8VY1FG4VFJsImVOOapUlP3RGwwMM829-bjXhSTAO4PAd9-e6lxBkLNJ2-Y8SGALBETJblIQUHT77X5teYWVUd8I2gfrQ5ma5Kjzm7InXECJV7-h5GfjxqBrXtawfBKVI3dp87ZmiRtSOc_ERd_HIS90ybAULpD4SExzypAgxW8UpMf4cv18jKN5t06p3Er8lW_Qj1hPcNm_7-lXlCQTHnKIxNfP--yil027KMLqytF5g_ihCyiOtqWn4MWSXG6ARMQw&token_type=Bearer&expires_in=3600 +``` + + +# To generate JWT using API Gateway + +To get the Cognito user pool JWT tokens, call the API gateway URL as: +curl -u '{CognitoUserName}:{CognitoUserPassword}' “{InvokeURL}/token” + +Ex. +```shell +curl -u 'test1@test.com:1234567890' https://1yq9tq9ojk.execute-api.us-east-1.amazonaws.com/token +``` +Result: +```shell +{ + "ResponseMetadata" : { + "RequestId" : "e4d3f251-a4ac-4963-8222-e555ef84097c", + "HTTPHeaders" : { + "date" : "Tue, 31 Oct 2023 08:44:24 GMT", + "content-type" : "application/x-amz-json-1.1", + "connection" : "keep-alive", + "x-amzn-requestid" : "e4d3f251-a4ac-4963-8222-e555ef84097c", + "content-length" : "4302" + }, + "RetryAttempts" : 0, + "HTTPStatusCode" : 200 + }, + "ChallengeParameters" : {}, + "AuthenticationResult" : { + "IdToken" : "eyJraWQiOiJROGoxOFZqd1wvZis0QU5DVHhVa2labXk5c3poVkNpSUlqdW5KdkZtY2NGMD0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI5NDU4MzQ4OC00MDgxLTcwNjYtN2U5NC1jM2IyMjk5ZWY1ZTEiLCJjb2duaXRvOmdyb3VwcyI6WyJzeXNhZG1pbiIsImJsdWVhZG1pbiJdLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0xLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMV9mbXlOdW1rMEwiLCJjb2duaXRvOnVzZXJuYW1lIjoiOTQ1ODM0ODgtNDA4MS03MDY2LTdlOTQtYzNiMjI5OWVmNWUxIiwib3JpZ2luX2p0aSI6IjM4NTliNmMwLTdlZDgtNDliZi04NmY3LTA0Zjg1YTM0YmIzZiIsImNvZ25pdG86cm9sZXMiOlsiYXJuOmF3czppYW06OjQ0Mjk1MjYzNzM3MTpyb2xlXC9yZHNyb2xlX2Zvcl9lYzIiLCJhcm46YXdzOmlhbTo6NDQyOTUyNjM3MzcxOnJvbGVcL3Jkc3JvbGUiXSwiYXVkIjoiN2xoZjE3anVpbzBmcnIydm1lMzhxcG5zb2IiLCJldmVudF9pZCI6ImU0ZDNmMjUxLWE0YWMtNDk2My04MjIyLWU1NTVlZjg0MDk3YyIsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNjk4NzQxODY0LCJleHAiOjE2OTg3NDU0NjQsImlhdCI6MTY5ODc0MTg2NCwianRpIjoiOWVmYTg1YWUtZTVhNS00YzIwLWFiYTktOTI1YjAxYTJmN2RmIiwiZW1haWwiOiJ0ZXN0MUB0ZXN0LmNvbSJ9.MQE04hRYHthQdvfi_mGIZ2-xQOibfc9nGIQ-k6NF_qTc7FpurLBtk_Cprb4Rrm-UEmMvsPwupB9_7RcyTTNgFxi909YitiVryqszq9iiGc0txdutrSuV-d4NSL-p1md-KlUMSQX3VLSdvybWnMVl2tIQmSOLMereYRdAueFcLyi7eCC2D7D7lEh8vqukWkeewVPJEez3qeDedzZB3iDARmh7NvavKPio3awK3AbBVzqSYpFd7uYn_7g4O1eUPymJwlvx81n6n-uUpPE7DMkWa7RiimHU6lq934K0HmachYniWJrTrBEzn24Tkb6Hx5kPXdZzmAGkdTA6oWcN1fxuVQ", + "RefreshToken" : "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.cR8uHz6No78KaEOXEUNmEa8DeovIVnoLiPHt8SBvf_iHkkfNRzNwD1mRXqvgfFIWZpRkDe-ob1cz_r8Ss_HGvs0OLMjNXCi6rzvb_ba2RTuwvBpphMR9hDwrMK6-XYdCC4PSyXpcp26obmFAYPU44ADl8UXDaSHZxi4-qjxcOsoz8coVcwglIIqYgfxK225Nh2idyglfGN94QRclmpS14QXWyDKkSN-P4B7IKMyRaCtTIYodggbxgu7DEfu1hbkREjYT4A0QD2eBeeRv7EJGjNtED2UvDjDwgs1YPcUdUq6hZvHATUgnJhwTR4UuDG3YufQ2_oqwj5owa_elWlot-Q.mkrs4TvKRsm7cSjl.K0GoU6m4cJXBfS2jDbX0UYEDUc0NluzPPdvc7yfU4vpWru8DaZE5DhshsX766OWKbHj9kAxytcXYmZIVushEKvaRCZXdVHj-3KGF8WP_bMWUuKKzqWYuKYTe0mSQ6a_obm7zY0iosk3L0cQa0EVA0qL8MLB_dJT6jZn_muPfYOKUfByIrOFGoVbir4SALuiQZAZviTNmOqyrutMvYwfuEbZJ2xGyywr_Waddzp_N0sMYsifLmr_oJ4p_wGdC4XPVo6OnHaKV1b0oEZvvtfScjjoRYDDUMJZIKMNanKWcQPVzEJ_pS9R42ImMC88smdYG6ZX_08WmymcWqwTtDDBz0iU8goE9_hUYPlNv_lPrVR6p-hyjstVW4FhreVt__kjHqo7YcmWPvwRz10C3QPBw2RRjxlge9XGwPbpF6lFqZF1LO3JXF5QglxJZpihHer0OwTQGRUbbdav5WWvnLS6JXRDNmoP5dYQN2Re0sPJr9hiwsmGzG291Q1yeI4osA4MnSrDCeGS4oVyelJWow4nhpV885xG2eneg9PQZYmvX2DyPPiwtzLX-MCE5Br72p8QeDDvbYMjMfYwQs8e-zVIy8YSNUsDkAq2jHcUbw-FCEfHLA53INiZPE37CjrluW7S2JPXHdP-1geuRgPSjJ7OANBFD2PFqILtQ-_GVQSk2A-yhJF78mk37tW7TfBihyZI_OGHWcoaGOxbFjys5GrPdxZLbRn73IrqcMo-yEbiRYVDM0ArPgdP0wns-wGTcBg47SwZ8LVkDaGesHWzX8CF74FIUM-pfedOFD89jLm1rrdUHsGMh9rsKlblD_cOXP4_FifUXWwH4fQ-dXVz1bM_u_JMyo1vW57Gb57WN_A4nozIrS2NDH6svoWaI7Hk6vSog9JJDo8gsPJgavpJdIumWqJIEHPeA0I_OgdKY5DRDXjDdgy8I8XF6ncr8ranguzDnEOovHrzqwNGeS9fRhTSQZaHV8_Rb4_ppnL2OkAQEmVNewuWitXKqGaPc6t39pg2Ec3TGu_pYtp2P8Oy3NYZMOjkZIKfPoN39RaDwW-O5VwwMaEUFYEy0nMOFr_ecCF1qu1jY81eWsd-tg67iRM9bs1mc66F7-0KOgJfWgnnjxcga0f1a5-pgfZZuTeNPkNRSNcwJcapdkOQgTSS4C9Mw3LLdTUvVygJqrDvLRaqaSO5b3nm-8aStki64I6Cs5WUuP-8HI0x2EXusWM3c_UQNePnRgLQ0uW9FxQdGP5nLqH_EMU0zy3EFEwGv-93tR5ITRZgiOZArwjsFfJFyDwH9xZc3JOqcKCpqPpxrqnzxcGh6ooGxBAOgD0r-oHo.k60SnYB8FWs6zluKOIRufg", + "AccessToken" : "eyJraWQiOiJHUFljVVEyUGQxT2JJVUlLWEVVR1NQbVhcLzVJU0NUZFY3OEs2TjJXVFdoUT0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI5NDU4MzQ4OC00MDgxLTcwNjYtN2U5NC1jM2IyMjk5ZWY1ZTEiLCJjb2duaXRvOmdyb3VwcyI6WyJzeXNhZG1pbiIsImJsdWVhZG1pbiJdLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAudXMtZWFzdC0xLmFtYXpvbmF3cy5jb21cL3VzLWVhc3QtMV9mbXlOdW1rMEwiLCJjbGllbnRfaWQiOiI3bGhmMTdqdWlvMGZycjJ2bWUzOHFwbnNvYiIsIm9yaWdpbl9qdGkiOiIzODU5YjZjMC03ZWQ4LTQ5YmYtODZmNy0wNGY4NWEzNGJiM2YiLCJldmVudF9pZCI6ImU0ZDNmMjUxLWE0YWMtNDk2My04MjIyLWU1NTVlZjg0MDk3YyIsInRva2VuX3VzZSI6ImFjY2VzcyIsInNjb3BlIjoiYXdzLmNvZ25pdG8uc2lnbmluLnVzZXIuYWRtaW4iLCJhdXRoX3RpbWUiOjE2OTg3NDE4NjQsImV4cCI6MTY5ODc0NTQ2NCwiaWF0IjoxNjk4NzQxODY0LCJqdGkiOiI0ZmFkYzkxNC05OGE5LTQxNzEtODIxMy1jNjFmYzU0ZTZmMDkiLCJ1c2VybmFtZSI6Ijk0NTgzNDg4LTQwODEtNzA2Ni03ZTk0LWMzYjIyOTllZjVlMSJ9.acEfiz0SB0GVDNR_7bmzjtqeN3XzRmGp7BBMU56ia9iDx6CGnEXOnPf9rFn7njGvZOhG-qjCGZHSTXERUv1DlCUJw_hJDhRnFSM-h6PjpBzGXo3JGDqY31j3hrtsBY9hQaYch1ZTvcPrElW0MBlZPdkXH826XK5cTNEKnZgzcMAPEYIQg4TNg_Sds47xg6VbSltA6iExeVjT7SOAc-5udif5wQzfNLTbVxxbW9s9WWC2hSiPhL2wvQbqMmh66DTRxCJFgy6utThy1APXW2cd7TDmpPoqXlmmfBK3OX0jBOzlwKNh5oRsUZHJpdpM09adCUwhes6dWq3Y-n9hDKnY5Q", + "ExpiresIn" : 3600, + "TokenType" : "Bearer" + } +} + +``` + diff --git a/aws/security_plugins/db2-aws-iam/Dockerfile b/aws/security_plugins/db2-aws-iam/Dockerfile new file mode 100644 index 0000000..7c3c168 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/Dockerfile @@ -0,0 +1,26 @@ +# Use Db2U RHEL8 base image as the base docker image + +FROM ibmcom/db2 +LABEL description="AWS Db2 AWS IAM Security Plugin" + +ARG OPENSSL_VER=1 +ENV OPENSSL_VER=$OPENSSL_VER + +COPY install_packages.sh /tmp/ +RUN chmod u+x /tmp/install_packages.sh && /tmp/install_packages.sh $OPENSSL_VER + +# Install required packages +RUN echo "jenkins ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers +RUN echo "jenk ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +# Build the source code +# RUN ./build + +WORKDIR /mnt/blumeta0/db2_config/ +COPY src/gss/users.json /mnt/blumeta0/db2_config/ +RUN chmod -R u+rwX,go+rwX /mnt/blumeta0/ + +WORKDIR /mnt/db2-aws-iam/src/ +RUN chmod -R u+rwX,go+rwX /mnt/db2-aws-iam/ + +RUN echo "export OPENSSL_VER=${OPENSSL_VER}" >> /etc/profile.d/db2_sec_plugin.sh diff --git a/aws/security_plugins/db2-aws-iam/Makefile b/aws/security_plugins/db2-aws-iam/Makefile new file mode 100644 index 0000000..2f39bb0 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/Makefile @@ -0,0 +1,31 @@ +.PHONY: all +all: + $(MAKE) -C src gss + +.PHONY: plugin +plugin: + $(MAKE) -C src gss + + +.PHONY: clean +clean: + $(MAKE) -C src cleangss + $(MAKE) -C src cleanum + + +.PHONY: install +install: + $(MAKE) -C src install + +.PHONY: remove +remove: + $(MAKE) -C src remove + +.PHONY: test +test: + $(MAKE) -C src/test + +.PHONY: tail +tail: + tailf ~/sqllib/db2dump/db2diag.log | grep gssapi + diff --git a/aws/security_plugins/db2-aws-iam/README.md b/aws/security_plugins/db2-aws-iam/README.md new file mode 100644 index 0000000..56950c9 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/README.md @@ -0,0 +1,179 @@ +## Build instructions + +One can build the plugin in a docker container or a baremetal with Linux RHEL 8, or AWS EC2 instance created from Amazon Linux 2. + + +### Pre-requisites +Db2 server or client must be installed on the build system. + +Note: As of now, neither Db2 nor this plugin supports AWS EC2 created from Amazon Linux 2023. + +### Build steps for non-container (or AWS EC2) + +To build plugin on a host system like EC2, go to db2-aws-iam directory + +1. Execute the `install_packages.sh` script to install all the needed dependencies with input as 1 or 3 for corresponding OpenSSL version as follows - + +```shell +cd db2-aws-iam +export OPENSSL_VER= +sudo sh install_packages.sh $OPENSSL_VER +``` + +2. Once the dependencies are installed, build AWS SDK using `build_aws_sdk.sh` script as follows - + +```shell +sh build_aws_sdk.sh $OPENSSL_VER +``` + +3. Build the plugin + +```shell +make +``` + +There is also a script `build.sh` in the current directory which does all the above steps in one go. + +### Build steps for container build + +1. Create the build container + +We need to specify OpenSSL version to be used by docker container for the builds of AWS SDK and security plugin. The `docker build` command below expects `1` or `3` as OPENSSL_VER value for OpenSSL 1.x and OpenSSL 3.x respectively. If nothing is specified, OpenSSL 1.x will be used by default, in the container. + +```shell + +cd db2-aws-iam +docker build --build-arg OPENSSL_VER= -t db2:awsplugin . +docker run -itd --name mydb2 --privileged=true -p 50000:50000 -e LICENSE=accept -e DB2INST1_PASSWORD=testpw -e DBNAME=bludb -v $PWD:/mnt/db2-aws-iam +``` + +Here, is to be replaced with Image ID created by `docker build . ` command. + +2. Build AWS CPP SDK in the container + +```shell +docker exec -ti mydb2 bash +cd /mnt/db2-aws-iam +sh build_aws_sdk.sh $OPENSSL_VER +``` + +3. Build the security plugin + +Connect to container using root user to change the permissions on the mounted volume where source code resides and then use `db2inst1` user for actual build. + +```shell +docker exec -ti mydb2 bash ------------------------- can skip this if user is already logged into the container +chmod -R u+rwX,go+rwX /mnt/db2-aws-iam/src/ +exit +docker exec -ti mydb2 bash -c "su - db2inst1" +declare -x DB2_HOME="${HOME}/sqllib" +cd /mnt/db2-aws-iam +make +``` + +Once the build is successful, the plugin libraries will be generated at `src/build/security64/plugin/IBM` and a tar file of all libraries with name `db2-aws-iam-secplugins.tar.gz` will be created at `src/output/`. + + +## Deploying the plugin + +The system where the plugin is deployed can be a different system than the build system. + +If the plugin is to be deployed on AWS EC2, follow the step below. Otherwise, this step can be skipped and instead AWS developer credentials should be set on a container or host. + +1. Create a role with below set of policies and attach that role to the EC2 instance. Replace {USERPOOL_ARN} with the arn of the user pool. + +```shell +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "cognito-idp:GetGroup", + "cognito-idp:AdminListGroupsForUser", + "cognito-idp:AdminGetUser", + "cognito-idp:ListUserPoolClients" + ], + "Resource": "{USERPOOL_ARN}" + } + ] +} +``` + +Note: If the plugin is deployed on a local Db2 container, the container needs to have AWS developer credentials configured, so that AWS APIs work. + +2. db2 terminate && db2stop + +3. Copy the AWS libraries + +Plugin needs AWS Cognito library which is not available in the Db2 installation. +Hence, one needs to copy the built Cognito library to Db2 installation directory. + +For e.g. +```shell +docker exec -ti mydb2 bash +sudo cp /usr/local/lib64/libaws-cpp-sdk-cognito-idp.so /opt/ibm/db2/V11.5/lib64/awssdk/RHEL/8.1/ +``` + +Create symlink for above library in /opt/ibm/db2/V11.5/lib64/ +```shell +cd /opt/ibm/db2/V11.5/lib64 +sudo ln -s awssdk/RHEL/8.1/libaws-cpp-sdk-cognito-idp.so libaws-cpp-sdk-cognito-idp.so +``` + +4. Copy the plugin libraries and update the Db2 configuration to enable the plugin + +The plugin tar which is created after the build of plugin at `db2-aws-iam/src/output/db2-aws-iam-secplugins.tar.gz` should be copied +to EC2 instance or Db2 container, at `/tmp`. + +```shell +docker exec -ti mydb2 bash +cd /opt/ibm/db2/V11.5 +tar -xvf /tmp/db2-aws-iam-secplugins.tar.gz +su - db2inst1 +/opt/ibm/db2/V11.5/configSecPlugin.sh +``` + +5. AWS Cognito userpool configuration +When this plugin is installed and enabled in Db2, one must also create a file as follows - + +```shell +mkdir -p ~/sqllib/security64/plugin/cfg +touch ~/sqllib/security64/plugin/cfg/cognito_userpools.json +``` + +The content of the file should be as following - + +```shell +$ cat ~/sqllib/security64/plugin/cfg/cognito_userpools.json +{ + "UserPools" : + { + "ID" : "eu-north-1_bOS6HFSKj", + "Name" : "TestPool" + } +} +``` +Userpool ID and Name should have value as per the AWS Cognito userpool created for Db2 users. Currently only one userpool is supported. +One can also change the location/filename of this file and set `AWS_USERPOOL_CFG_ENV` variable accordingly. + + +```shell +export AWS_USERPOOL_CFG_ENV=security64/plugin/cfg/cognito_userpools.json +``` +This path should be relative to DB2_HOME variable. + +6. db2start + + +## Connect to Db2 using TOKEN generated by AWS Cognito + +Refer [`AWS_cognito.md`](AWS_cognito.md) to know how to setup userpool, create users and groups in AWS, and how to retrieve tokens. +Once the token is retrieved from AWS for a user, user can use it for connecting to Db2. + +Below command is used to connect to Db2 - +```shell +TOKEN="" +db2 connect to ACCESSTOKEN $TOKEN +``` diff --git a/aws/security_plugins/db2-aws-iam/build.sh b/aws/security_plugins/db2-aws-iam/build.sh new file mode 100755 index 0000000..25de06c --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/build.sh @@ -0,0 +1,9 @@ +set -ex + +OPENSSL_VER=$1 +# Install the dependent packages +sh $PWD/install_packages.sh $OPENSSL_VER + +sh $PWD/build_aws_sdk.sh $OPENSSL_VER + +make clean && make diff --git a/aws/security_plugins/db2-aws-iam/build_aws_sdk.sh b/aws/security_plugins/db2-aws-iam/build_aws_sdk.sh new file mode 100755 index 0000000..5ec9117 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/build_aws_sdk.sh @@ -0,0 +1,43 @@ +set -exou + +OPENSSL_VER=1 +if [[ $# == 1 ]]; then + OPENSSL_VER=$1 + case "$OPENSSL_VER" in + 1) echo "You chose openssl 1.x." + $PWD/install_packages.sh 1 + ;; + 3) echo "You chose openssl 3.x." + $PWD/install_packages.sh 3 + ;; + *)echo "Usage: `basename ${0}` 1|3" + exit 1 + ;; + esac +else + echo "Please specify the OpenSSL version to be used for build. Specify 1 for OpenSSL 1.x and 3 for OpenSSL 3.x." + echo "If no argument is provided, by default OpenSSL 1.x will be used for builds." +fi + +rm -rf aws-sdk-cpp + +git clone https://github.com/aws/aws-sdk-cpp -b 1.9.247 +cd aws-sdk-cpp +git submodule update --init --recursive +mkdir build +cd build/ +CXX_FLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" +OPENSSL_FLAGS="" + +if [[ $OPENSSL_VER == 3 ]]; then + CXX_FLAGS+=" -Wno-error=deprecated-declarations" + OPENSSL_FLAGS="-DOPENSSL_ROOT_DIR=/usr/ -DOPENSSL_SSL_LIBRARY=/usr/lib64 -DOPENSSL_INCLUDE_DIR=/usr/include/openssl3" +fi + +cmake .. -D_GLIBCXX_USE_CXX11_ABI=0 -DCMAKE_CXX_FLAGS="$CXX_FLAGS" $OPENSSL_FLAGS -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ -DBUILD_ONLY="cognito-idp;cognito-identity;s3;transfer" -DENABLE_TESTING=OFF +make clean && make + +sudo make install +cd ../.. + +rm -rf aws-sdk-cpp diff --git a/aws/security_plugins/db2-aws-iam/install_aws_sdk.sh b/aws/security_plugins/db2-aws-iam/install_aws_sdk.sh new file mode 100644 index 0000000..a894107 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/install_aws_sdk.sh @@ -0,0 +1,6 @@ + +sudo cp /usr/local/lib64/libaws-cpp-sdk-cognito-idp.so /opt/ibm/db2/V11.5/lib64/awssdk/RHEL/8.1/ + +cd /opt/ibm/db2/V11.5/lib64 +sudo ln -s awssdk/RHEL/8.1/libaws-cpp-sdk-cognito-idp.so libaws-cpp-sdk-cognito-idp.so + diff --git a/aws/security_plugins/db2-aws-iam/install_packages.sh b/aws/security_plugins/db2-aws-iam/install_packages.sh new file mode 100755 index 0000000..0bbb234 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/install_packages.sh @@ -0,0 +1,26 @@ +set -exou + +OPENSSL_VER=$1 + +sudo yum install -y which make gcc-c++ perl json-c-devel libcurl-devel openldap-devel git sudo cmake3 +ARCH=$(uname -r) +if [[ $OPENSSL_VER == 3 ]]; +then + if [[ "$ARCH" =~ "amzn2023" ]] ; then + sudo yum install -y openssl openssl-devel openssl-libs + elif [[ "$ARCH" =~ "el8" ]]; then + sudo yum install -y openssl3 openssl3-devel openssl3-libs + fi + sudo rm -f /usr/lib64/libcrypto.so + sudo ln -s /usr/lib64/libcrypto.so.3 /usr/lib64/libcrypto.so +else + sudo yum install -y openssl openssl-devel openssl-libs +fi + +if [[ ! -e /usr/lib64/libldap.so.2 ]]; then + sudo ln -s "/usr/lib64/libldap.so" /usr/lib64/libldap.so.2 +fi + +if [[ ! -e /usr/bin/cmake ]]; then + sudo ln -s /usr/bin/cmake3 /usr/bin/cmake +fi diff --git a/aws/security_plugins/db2-aws-iam/src/Makefile b/aws/security_plugins/db2-aws-iam/src/Makefile new file mode 100755 index 0000000..1ab4d2c --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/Makefile @@ -0,0 +1,171 @@ + +OBJDIR := obj +BUILDDIR := build +OUTPUTDIR := output +PLUGINDIR := $(BUILDDIR)/security64/plugin/IBM +TEST_DIR := test + +# need to be built by instance owner +# $DB2_HOME points to expanded ~/sqllib +PRINCIPAL_NAME := AWSIAMauth +GSSAPI_CLIENT_MODULE := $(PLUGINDIR)/client/$(PRINCIPAL_NAME).so +GSSAPI_SERVER_MODULE := $(PLUGINDIR)/server/$(PRINCIPAL_NAME).so +GROUP_MODULE := $(PLUGINDIR)/group/$(PRINCIPAL_NAME)group.so +AWS_SDK_LIBS := -L/usr/local/lib64 -laws-cpp-sdk-core -laws-cpp-sdk-cognito-idp -Wl,-rpath,/opt/ibm/db2/V11.5/lib64 +CPPLIBS := -lstdc++ + +# Check the installed version of openssl and json_c and according set the macro +INSTALLED_OPENSSL := $(shell yum info installed openssl | grep Version | sed -e 's/Version\s*:\s*//g' | sed -e 's/[a-z]-*.*//' | sed -e 's/\.//g') +INSTALLED_JSON_C := $(shell yum info installed json-c | grep Version | sed -e 's/Version\s*:\s*//g' | sed -e 's/\.//g') +$(info INSTALLED_OPENSSL is $(INSTALLED_OPENSSL)) +$(info INSTALLED_JSON_C is $(INSTALLED_JSON_C)) + +OSSL_MACRO := +VER_GT_110 = $(shell echo $(INSTALLED_OPENSSL)\>=110 | bc ) +ifeq ($(VER_GT_110), 1) +OSSL_MACRO += -DOPENSSL_1_1_0 +endif + +JSON_MACRO := +VER_GT_0_13 = $(shell echo $(INSTALLED_JSON_C)\>=0130 | bc ) +ifeq ($(VER_GT_0_13), 1) +JSON_MACRO += -DJSON_C_0_13 +endif + +_dummy := $(shell mkdir -p $(OBJDIR)) +_dummy := $(shell mkdir -p $(OUTPUTDIR)) +_dummy := $(shell mkdir -p $(PLUGINDIR)/server) +_dummy := $(shell mkdir -p $(PLUGINDIR)/client) +_dummy := $(shell mkdir -p $(PLUGINDIR)/group) + +.DEFAULT_GOAL := all + +CXXFLAGS := -D_GLIBCXX_USE_CXX11_ABI=0 + +INCLUDES := -I$(DB2_HOME)/include -I. +OSSL_LIBS_PATH := -L/usr/lib64 + +ifeq ($(OPENSSL_VER), 3) + INCLUDES := -I$(DB2_HOME)/include -I. -I/usr/include/openssl3 + OSSL_LIBS_PATH := -L/usr/lib64 +endif + +CFLAGS := $(INCLUDES) -g -fpic -fno-strict-aliasing -Werror \ + -DSQLUNIX \ + -D_GLIBCXX_USE_CXX11_ABI=0 \ + -D_REENTRANT \ + $(OSSL_MACRO) \ + $(JSON_MACRO) \ + -DCRYPT_OPENSSL_SUPPORTED + + +GSSAPI_CLIENT_SRC := AWSIAMauthclient.c AWSIAMauthcommon.c AWSIAMtrace.c base64.c +GSSAPI_CLIENT_OBJ := $(addprefix $(OBJDIR)/,$(notdir $(GSSAPI_CLIENT_SRC:.c=.o))) + +GSSAPI_SERVER_SRC := AWSIAMauthserver.c AWSIAMauthcommon.c jwt.c jwk.c base64.c AWSIAMtrace.c utils.c +GSSAPI_SERVER_OBJ := $(addprefix $(OBJDIR)/,$(notdir $(GSSAPI_SERVER_SRC:.c=.o))) + +GSSAPI_SERVER_SRC2 := AWSUserPoolInfo.cpp iam.cpp AWSSDKRAII.cpp +GSSAPI_SERVER_OBJ2 := $(addprefix $(OBJDIR)/,$(notdir $(GSSAPI_SERVER_SRC2:.cpp=.o))) + +GROUP_SRC := AWSIAMauthgroup.c AWSIAMauthcommon.c AWSIAMtrace.c jwt.c jwk.c base64.c usersjson.c utils.c +GROUP_OBJ := $(addprefix $(OBJDIR)/,$(notdir $(GROUP_SRC:.c=.o))) + +GROUP_SRC2 := AWSUserGroupInfo.cpp AWSSDKRAII.cpp +GROUP_OBJ2 := $(addprefix $(OBJDIR)/,$(notdir $(GROUP_SRC2:.cpp=.o))) + + +LIBS := -lcrypto -lcurl -ljson-c -lldap $(OSSL_LIBS_PATH) +GROUP_LIBS := -lcrypto -Wl,-z,defs +GROUP_LIBSJ := -ljson-c + +$(GSSAPI_CLIENT_MODULE): $(GSSAPI_CLIENT_OBJ) + $(CC) $(CFLAGS) $(LIBS) -shared $^ -o $@ + chmod 755 $@ + +$(GSSAPI_SERVER_MODULE): $(GSSAPI_SERVER_OBJ) $(GSSAPI_SERVER_OBJ2) + $(CC) $(CFLAGS) $(LIBS) $(CPPLIBS) $(AWS_SDK_LIBS) -shared $^ -o $@ + chmod 755 $@ + +$(GROUP_MODULE): $(GROUP_OBJ) $(GROUP_OBJ2) + $(CC) $(CFLAGS) $(LIBS) $(CPPLIBS) $(AWS_SDK_LIBS) -Wl,-z,defs -shared $^ -o $@ + chmod 755 $@ + + +.PHONY: gss +gss: $(OUTPUTDIR)/db2-aws-iam-secplugins.tar.gz + +$(OUTPUTDIR)/db2-aws-iam-secplugins.tar.gz: $(GSSAPI_CLIENT_MODULE) $(GSSAPI_SERVER_MODULE) $(GROUP_MODULE) $(BUILDDIR)/configSecPlugin.sh + tar -C $(BUILDDIR) --owner=bin --group=bin -cvf $(OUTPUTDIR)/db2-aws-iam-secplugins.tar . + tar -C $(BUILDDIR) --owner=1500 --group=1500 -rvf $(OUTPUTDIR)/db2-aws-iam-secplugins.tar + gzip -f $(OUTPUTDIR)/db2-aws-iam-secplugins.tar + + +$(BUILDDIR)/configSecPlugin.sh: configSecPlugin.sh + sed 's/__PRINCIPAL_NAME__/$(PRINCIPAL_NAME)/g' configSecPlugin.sh > $(BUILDDIR)/configSecPlugin.sh + chmod 755 $@ + +.PHONY: cleangss +cleangss: + $(RM) $(GSSAPI_CLIENT_OBJ) $(GSSAPI_SERVER_OBJ) $(GSSAPI_SERVER_OBJ2) $(GROUP_OBJ) $(GROUP_OBJ2) $(GSSAPI_SERVER_MODULE) $(GSSAPI_CLIENT_MODULE) $(GROUP_MODULE) \ + $(UNIT_OBJ) $(UNIT_TEST) $(OUTPUTDIR)/db2-aws-iam-secplugins.tar.gz $(BUILDDIR)/configSecPlugin.sh + +$(OBJDIR)/%.o: gss/%.c + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJDIR)/%.o: commmon/%.c + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJDIR)/%.o: common/%.c common/%.h + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJDIR)/%.o: gss/%.c gss/%.h + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJDIR)/iam.o: gss/iam.cpp gss/iam.h + $(CC) $(CFLAGS) $(CXXFLAGS) -c $< -o $@ + +$(OBJDIR)/AWSUserGroupInfo.o: gss/AWSUserGroupInfo.cpp gss/AWSUserGroupInfo.h + $(CXX) $(CFLAGS) $(CXXFLAGS) -I/usr/local/include $(CPPLIBS) $(AWS_SDK_LIBS) -c $< -o $@ + +$(OBJDIR)/AWSUserPoolInfo.o: gss/AWSUserPoolInfo.cpp gss/AWSUserPoolInfo.h + $(CXX) $(CFLAGS) $(CXXFLAGS) -I/usr/local/include $(CPPLIBS) $(AWS_SDK_LIBS) -c $< -o $@ + +$(OBJDIR)/AWSSDKRAII.o: gss/AWSSDKRAII.cpp gss/AWSSDKRAII.h + $(CXX) $(CFLAGS) $(CXXFLAGS) -I/usr/local/include $(CPPLIBS) $(AWS_SDK_LIBS) -c $< -o $@ + +$(OBJDIR)/AWSIAMauthgroup.o: gss/AWSIAMauthgroup.c + $(CC) $(CFLAGS) -z defs -c $< -o $@ + +$(OBJDIR)/AWSIAMtrace.o: common/AWSIAMtrace.c + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJDIR)/usersjson.o: common/usersjson.c + $(CC) $(CFLAGS) -c $< -o $@ + +-include $(wildcard *.d) + +.PHONY: install +install: $(OUTPUTDIR)/db2-aws-iam-secplugins.tar.gz + sudo tar -C ~/sqllib -xvf $(OUTPUTDIR)/db2-aws-iam-secplugins.tar.gz + sh ~/sqllib/configSecPlugin.sh + db2 terminate; db2stop force; db2start; db2 activate db bludb + +.PHONY: remove +remove: + sh ~/sqllib/configSecPlugin.sh -remove + sudo $(RM) ~/sqllib/security64/plugin/IBM/client/$(notdir ${GSSAPI_CLIENT_MODULE}) + sudo $(RM) ~/sqllib/security64/plugin/IBM/server/$(notdir ${GSSAPI_SERVER_MODULE}) + sudo $(RM) ~/sqllib/security64/plugin/IBM/group/$(notdir ${GROUP_MODULE}) + db2 terminate; db2stop force; db2start; db2 activate db bludb + +.PHONY: tail +tail: + tailf ~/sqllib/db2dump/db2diag.log | grep AWSIAMauth + + +.PHONY: cleanum +DB2PATH = $(HOME)/sqllib +ERASE = rm -f +cleanum : + $(ERASE) obj/*.o diff --git a/aws/security_plugins/db2-aws-iam/src/common/AWSIAMtrace.c b/aws/security_plugins/db2-aws-iam/src/common/AWSIAMtrace.c new file mode 100644 index 0000000..44fec38 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/common/AWSIAMtrace.c @@ -0,0 +1,77 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/AWSIAMtrace.c (%W%) +** +** Descriptive Name = trace functionality +** +** Function: +** +** Dependencies: +** +** Restrictions: +** +*****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include + +#include "AWSIAMtrace.h" + + + +#define DB2OC_TRACE_ON_FILE "/tmp/iam_trace_on.cfg" +#define DB2OC_TRACE_FILE "/tmp/IAM_DEBUG.trc" + +void printToDebugFile(TRACE_POINT_TYPE type, const char *pszFile, const char *pszFunc, const char *pszData, int iData) +{ + FILE *pFile = NULL; + char szOutput[4024] = ""; + struct stat buffer; + + pid_t tid = syscall(SYS_gettid); + // return immediately if trace is not on. + if(stat(DB2OC_TRACE_ON_FILE, &buffer) !=0 ) + { + return; + } + + // Trace output will always go to + pFile=fopen(DB2OC_TRACE_FILE, "a"); + switch(type) + { + case Ientry: + snprintf(szOutput,sizeof(szOutput),"%d:%lu:ENTRY->-%s:%s\n",tid, (unsigned long)time(NULL), pszFile, pszFunc); break; + case Iexit: + snprintf(szOutput,sizeof(szOutput),"%d:%lu:EXIT->-%s:%s (%d)\n",tid, (unsigned long)time(NULL), pszFile, pszFunc,iData); break; + case Idata: + if(pszData == NULL) + { + snprintf(szOutput,sizeof(szOutput),"%d:%lu:DATA->-%s:%s (%s)\n",tid, (unsigned long)time(NULL), pszFile, pszFunc,"NULL"); + } + else + { + snprintf(szOutput,sizeof(szOutput),"%d:%lu:DATA->-%s:%s (%s)\n",tid, (unsigned long)time(NULL), pszFile, pszFunc,pszData); + } + break; + } + + fputs(szOutput, pFile); + fflush(pFile); + fclose(pFile); +} diff --git a/aws/security_plugins/db2-aws-iam/src/common/AWSIAMtrace.h b/aws/security_plugins/db2-aws-iam/src/common/AWSIAMtrace.h new file mode 100644 index 0000000..c38bc55 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/common/AWSIAMtrace.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = common/AWSIAMtrace.h +** +** Descriptive Name = FILE utility functions header file +** +** Function: Functions for communicating with file +** +** Dependencies: None +** +** Restrictions: None +* +* +*******************************************************************************/ +#ifndef _H_DB2_AWSIAMTRACE +#define _H_DB2_AWSIAMTRACE + + +#ifdef __cplusplus +#define DB2FILE_EXT_C extern "C" +#else +#define DB2FILE_EXT_C +#endif + +typedef enum TRACE_POINT_TYPE {Ientry, Iexit, Idata} TRACE_POINT_TYPE; + +DB2FILE_EXT_C void printToDebugFile(TRACE_POINT_TYPE type, const char *pszFile, const char *pszFunc, const char *pszData, int iData); + +#define IAM_TRACE_ENTRY(a) printToDebugFile(Ientry, __FILE__, a, NULL, 0) +#define IAM_TRACE_EXIT(a,b) printToDebugFile(Iexit, __FILE__, a, NULL, b) +#define IAM_TRACE_DATA(a,b) printToDebugFile(Idata, __FILE__, a, b, 0) + + +#endif // _H_DB2_AWSIAMTRACE diff --git a/aws/security_plugins/db2-aws-iam/src/common/base64.c b/aws/security_plugins/db2-aws-iam/src/common/base64.c new file mode 100644 index 0000000..7edbdde --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/common/base64.c @@ -0,0 +1,116 @@ +/* base64.c -- routines to encode/decode base64 data */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 1998-2018 The OpenLDAP Foundation. + * Portions Copyright 1998-2003 Kurt D. Zeilenga. + * Portions Copyright 1995 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ +/* Portions Copyright (c) 1996, 1998 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ +/* This work is based upon Base64 routines (developed by IBM) found + * Berkeley Internet Name Daemon (BIND) as distributed by ISC. They + * were adapted for inclusion in OpenLDAP Software by Kurt D. Zeilenga. + */ + +#include "base64.h" +#include +#include +#include +#include + + + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/************************************************************************* +* +* Function Name = base64_decode +* +* Descriptive Name = Base64 decoding function +* +* Dependencies = +* +* Restrictions = +* +* Input = in - input string to be decoded +* in_len - input string length +* out - Base64 decoded string(this variable must be freed +* by a caller) +* +* Output = +* +* Normal Return = nonzero length of the decoded string +* +* Error Return = 0 as the length of the decoded string +* +***************************************************************************/ +size_t base64_decode(const char *in, size_t in_len, unsigned char **out) +{ + size_t out_len = (in_len*3)/4; + unsigned char *decoded = NULL; + int bits_collected = 0; + unsigned int accumulator = 0; + size_t outpos = 0, i = 0; + + if( !out ) + { + return 0; + } + decoded = calloc(sizeof(char), out_len + 1); + if( !decoded ) + { + return 0; + } + + for(i = 0, outpos = 0; i < in_len; ++i) + { + const char c = in[i]; + if( c == '=' ) + { + continue; + } + + if ( c >= CODE_BOOK_SIZE || c < 0 || (reverse_table_url_safe[c] == -1) ) + { + // invalid character, early exit + free(decoded); + return 0; + } + // valid code book goes up to 6 bits + accumulator = (accumulator << 6) | reverse_table_url_safe[c]; + bits_collected += 6; + if( bits_collected >= 8 ) + { + bits_collected -= 8; + decoded[outpos++] = (char)((accumulator >> bits_collected) & 0xffu); + } + } + *out = decoded; + return outpos; +} + diff --git a/aws/security_plugins/db2-aws-iam/src/common/base64.h b/aws/security_plugins/db2-aws-iam/src/common/base64.h new file mode 100644 index 0000000..f0311d6 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/common/base64.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/base64.h (%W%) +** +** Descriptive Name = Header file for Base64 encoding code (base64.h) +** +** Function: +** +** Dependencies: +** +** Restrictions: +** +*****************************************************************************/ + +#ifndef _BASE64_H_ +#define _BASE64_H_ +#include +#include + + + + + + +#define CODE_BOOK_SIZE 128 + +static const char reverse_table_url_safe[CODE_BOOK_SIZE] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 +}; + +size_t base64_decode(const char *in, size_t in_len, unsigned char **out); + +/* for url safe decoding per RFC 4648 sectoin 5 */ +static const char reverse_table_url_safe_file[CODE_BOOK_SIZE] = { + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 62, 127, 127, 127, 63, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 127, 127, + 127, 64, 127, 127, 127, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 127, 127, 127, 127, 127, 127, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 127, 127, 127, 127, 127 +}; + +static const char b64_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +}; + + +#endif diff --git a/aws/security_plugins/db2-aws-iam/src/common/usersjson.c b/aws/security_plugins/db2-aws-iam/src/common/usersjson.c new file mode 100644 index 0000000..bc24e3b --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/common/usersjson.c @@ -0,0 +1,127 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/common/usersjson.c (%W%) +** +** Descriptive Name = Code for handling users.json file (usersjson.c) +** +** Function: +** +** Dependencies: +** +** Restrictions: +** +*****************************************************************************/ + +#include +#include +#include "usersjson.h" +#include "../gss/utils.h" + +/******************************************************************************* +* +* Function Name = DumpUsersJson +* +* Function = +* Dependencies = None +* +* Restrictions = None +* +* Input = fileDestination - path to file +* logFunc - function used for logging errors +* +* Output = Writes to a file with the current users.json content +* +*******************************************************************************/ +void DumpUsersJson(const char* fileDestination, db2secLogMessage* logFunc) +{ + IAM_TRACE_ENTRY("DumpUsersJson"); + + char dumpMsg[128] = ""; + int rc = DB2SEC_PLUGIN_OK; + FILE* f = fopen(fileDestination, "r"); + if(!f) { + rc = DB2SEC_PLUGIN_UNEXPECTED_SYSTEM_ERROR; + goto exit; + } + + fseek(f, 0L, SEEK_END); + size_t fileSize = ftell(f); + char* message = (char*) malloc(fileSize + 1); + rewind(f); + + if(message == NULL) { + rc = DB2SEC_PLUGIN_NOMEM; + goto exit; + } + + if(fread(message, fileSize, 1, f) != 1) { + rc = DB2SEC_PLUGIN_UNEXPECTED_SYSTEM_ERROR; + goto exit; + } + + FILE* d = fopen(DB2OC_USER_REGISTRY_ERROR_FILE, "w"); + if(!d){ + rc = DB2SEC_PLUGIN_UNEXPECTED_SYSTEM_ERROR; + goto exit; + } + + if(fprintf(d, message) < 0){ + rc = DB2SEC_PLUGIN_UNEXPECTED_SYSTEM_ERROR; + goto exit; + } + +exit: + if(f){ + fclose(f); + } + + if(message != NULL){ + free(message); + } + + if(d){ + fclose(d); + } + + if(rc != DB2SEC_PLUGIN_OK){ + char dumpMsg[32]; + snprintf(dumpMsg, sizeof(dumpMsg), "Unable to dump users.json!"); + logFunc(DB2SEC_LOG_ERROR, dumpMsg, strlen(dumpMsg)); + } + IAM_TRACE_EXIT("DumpUsersJson", rc); +} + +/******************************************************************************* +* +* Function Name = UsersJsonErrorMsg +* +* Function = +* Dependencies = None +* +* Restrictions = None +* +* Input = fnName - Name of error function +* errorMessage - Message that will be sent to db2diag +* errorMessageLength - Length of the error message +* +* Output = None +* +*******************************************************************************/ +void UsersJsonErrorMsg(const char* fnName, char** errorMessage, db2int32* errorMessageLength, const char* lstError) { + char dumpMsg[1024] =""; + snprintf(dumpMsg, sizeof(dumpMsg), "%s - unable to parse registry file %s: %s.", fnName, DB2OC_USER_REGISTRY_FILE, lstError); + *errorMessage = strdup(dumpMsg); + *errorMessageLength = strlen(*errorMessage); +} diff --git a/aws/security_plugins/db2-aws-iam/src/common/usersjson.h b/aws/security_plugins/db2-aws-iam/src/common/usersjson.h new file mode 100644 index 0000000..ac62565 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/common/usersjson.h @@ -0,0 +1,42 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/common/usersjson.h (%W%) +** +** Descriptive Name = Header file for Code that handles users.json file (usersjson.h) +** +** Function: +** +** Dependencies: +** +** Restrictions: +** +*****************************************************************************/ +#include +#include +#include +#include +#include "db2secPlugin.h" +#include "../gss/AWSIAMauthfile.h" +#include "AWSIAMtrace.h" + +#ifdef __cplusplus +extern "C" { +#endif +void DumpUsersJson(const char* fileDestination, db2secLogMessage* logFunc); +void UsersJsonErrorMsg(const char* fnName, char** errorMessage, db2int32* errorMessageLength, const char* lstError); +#ifdef __cplusplus +}; +#endif + diff --git a/aws/security_plugins/db2-aws-iam/src/configSecPlugin.sh b/aws/security_plugins/db2-aws-iam/src/configSecPlugin.sh new file mode 100755 index 0000000..f3c947f --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/configSecPlugin.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +PRINCIPAL_NAME=__PRINCIPAL_NAME__ +INSTANCE_OWNER=db2inst1 + + +if [ "$USER" != "$INSTANCE_OWNER" ]; then + echo "Script must be run by db2inst1" + exit 1 +fi + +if [ "$1" == "-remove" ]; then + db2 update dbm cfg using AUTHENTICATION SERVER + db2 update dbm cfg using srvcon_auth NOT_SPECIFIED + db2 update dbm cfg using srvcon_gssplugin_list NULL + db2 update dbm cfg using group_plugin NULL + db2 update dbm cfg using SRVCON_PW_PLUGIN NULL +else + db2 update dbm cfg using srvcon_gssplugin_list ${PRINCIPAL_NAME} + db2 update dbm cfg using srvcon_auth GSS_SERVER_ENCRYPT + db2 update dbm cfg using LOCAL_GSSPLUGIN ${PRINCIPAL_NAME} + db2 update dbm cfg using AUTHENTICATION GSSPLUGIN + db2 update dbm cfg using srvcon_auth GSS_SERVER_ENCRYPT + db2 update dbm cfg using group_plugin ${PRINCIPAL_NAME}group + #db2 update dbm cfg using sysadm_group NULL + db2set DB2AUTH=OSAUTHDB,ALLOW_LOCAL_FALLBACK,PLUGIN_AUTO_RELOAD +fi + + diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauth.h b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauth.h new file mode 100644 index 0000000..d6b84fa --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauth.h @@ -0,0 +1,201 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +***************************************************************************** +** +** Source File Name = src/gss/AWSIAMauth.h +** +** Descriptive Name = Base header file for AWS IAM authentication plugin libraries +** +** +** +** +*******************************************************************************/ +#ifndef _AWS_IAM_AUTH_H +#define _AWS_IAM_AUTH_H +#include +#include +#include +#include +#include + +#ifdef SQLUNIX +#include +#include +#else +#define strcasecmp(a,b) stricmp(a,b) +#define snprintf _snprintf +#endif + +#include +#include + +/* Authentication types */ +#define DB2SEC_AUTH_PASSWORD 0 // Authenticate using user/password +#define DB2SEC_AUTH_APIKEY 1 // Authenticate using API key +#define DB2SEC_AUTH_ACCESS_TOKEN 2 // Authenticate using access token + +static const char *authTypeToString[] = +{ + "DB2SEC_AUTH_PASSWORD", + "DB2SEC_AUTH_APIKEY", + "DB2SEC_AUTH_ACCESS_TOKEN" +}; + +static db2secLogMessage *logFunc; + +#define PRINCIPAL_NAME "AWSIAMauth" + +/* Minor Status Codes + * Ordering of messages is important as it corresponds to the + * appropriate minor status codes + */ +static const char *retcodeMessage[] = +{ + "Operation completed successfully", /* RETCODE_OK */ + "Credential failed authenticaiton", /* RETCODE_BADPASS */ + "Unexpected input token provided", /* RETCODE_BADTOKEN */ + "Mutual authentication failure", /* RETCODE_MUTFAIL */ + "Illegal call to gss_init/accept_sec_context", /* RETCODE_PROGERR */ + "Memory allocation error", /* RETCODE_MALLOC */ + "Error processing user definition file", /* RETCODE_USERFILE */ + "No credentials provided", /* RETCODE_NOCRED */ + "The database connection is not successful", /* RETCODE_BADCONNECTION */ + "Error description not available" /* RETCODE_UNKNOWN */ +}; + +#define RETCODE_OK 0 +#define RETCODE_BADPASS 1 +#define RETCODE_BADTOKEN 2 +#define RETCODE_MUTFAIL 3 +#define RETCODE_PROGERR 4 +#define RETCODE_MALLOC 5 +#define RETCODE_USERFILE 6 +#define RETCODE_NOCRED 7 +#define RETCODE_BADCONNECTION 8 +#define RETCODE_UNKNOWN 9 +#define RETCODE_AWS_NO_USER_ATTRI 10 +#define RETCODE_BADCFG 11 +#define RETCODE_MAXCODE RETCODE_UNKNOWN + +#define GSS_S_AZURE_COMPLETE 1 + +#define MAJOR_CODE_STRING PRINCIPAL_NAME " plugin encounted an error" + +#define SQL_AUTH_IDENT 128 + +/* Format of the token */ +#define TOKEN_MAX_STRLEN 1096 // AWS JWT token max length +#define TOKEN_MAX_AUTH_TOKEN_LEN 8192 // IAM access can have theoretically +#define IP_BUFFER_SIZE 16 // IP address + +// _authInfo is used to carry the different types of credentials to be flown +// between client and server security plugin +typedef struct _authInfo +{ + OM_uint32 version; + OM_uint32 authType; + OM_uint32 authTokenLen; + OM_uint32 useridLen; + char data[1]; +} AUTHINFO_VERSION_1_T ; + +#define AUTHINFO_VERSION_1 1 +#define AUTHINFO_T AUTHINFO_VERSION_1_T +typedef struct _name +{ + int useridLen; + char *userid; +} NAME_T; + +// Our GSS-API credential +typedef struct _cred +{ + int authtype; + int useridLen; + char *userid; + int authtokenLen; + char *authtoken; +} CRED_T; + +typedef struct _group_name { + char *group_name; + size_t len; +} group_name_t; + +typedef struct _context +{ + int sourceLen; + char *source; + int targetLen; + char *target; + int ctxCount; + int groupCount; + group_name_t* groups; +} CONTEXT_T; + +OM_uint32 getClientIPAddress(char szIPAddress[], int maxLength); +int ByteReverse(int input); +SQL_API_RC SQL_API_FN FreeErrorMessage(char *errormsg); +OM_uint32 SQL_API_FN gss_release_cred(OM_uint32 *minorStatus, + gss_cred_id_t *pCredHandle); +OM_uint32 SQL_API_FN gss_release_name(OM_uint32 *minorStatus, + gss_name_t *name); +OM_uint32 SQL_API_FN gss_release_buffer(OM_uint32 *minorStatus, + gss_buffer_t buffer); + +OM_uint32 SQL_API_FN gss_delete_sec_context(OM_uint32 *minorStatus, + gss_ctx_id_t *context_handle, + gss_buffer_t output_token); + +OM_uint32 SQL_API_FN gss_display_status(OM_uint32 *minor_status, + OM_uint32 status_value, + int status_type, + const gss_OID mech_type, + OM_uint32 *message_context, + gss_buffer_t status_string); + +SQL_API_RC SQL_API_FN PluginTerminate(char **errorMsg, + db2int32 *errorMsgLen); + +void delete_context(CONTEXT_T *pCtx); + + + + +#define db2Log(logType, fmt, ...) do { \ + char buffer[4096]; \ + db2int32 len = snprintf(buffer, 4096, "AWSIAMauth::" fmt, ##__VA_ARGS__); \ + openlog( PRINCIPAL_NAME, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_AUTHPRIV ); \ + if( logType == DB2SEC_LOG_INFO ) \ + { \ + syslog( LOG_INFO, "INFO AWSIAMauth::" fmt, ##__VA_ARGS__ ); \ + } \ + else if( logType == DB2SEC_LOG_WARNING ) \ + { \ + syslog( LOG_WARNING, "WARNING AWSIAMauth::" fmt, ##__VA_ARGS__ ); \ + } \ + else \ + { \ + syslog( LOG_ERR, "ERROR AWSIAMauth::" fmt, ##__VA_ARGS__ ); \ + } \ + closelog(); \ + if(logFunc) \ + { \ + logFunc(logType, buffer, len > 4095 ? 4095 : len ); \ + } \ + else \ + { \ + printf("\nLOG:TYPE = %d, message=%s\n", logType, buffer); \ + } \ +} while (0) + +#endif //_AWS_IAM_AUTH_H diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthclient.c b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthclient.c new file mode 100644 index 0000000..269459b --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthclient.c @@ -0,0 +1,1050 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +***************************************************************************** +** +** Source File Name = src/gss/AWSIAMauthclient.c +** +** Descriptive Name = Client-side AWS IAM authentication plugin +** +** Function: Implements client-side functions required by Db2 security +** plugin architecture +** +** +*******************************************************************************/ + +#include "AWSIAMauth.h" +#include "../common/AWSIAMtrace.h" +#include "pwd.h" + + +/****************************************************************************** +* +* Function Name = FreeToken +* +* Descriptive Name = Free Memory used for the token +* +* Function = We don't need to free anything as no token is used +* +* Dependencies = None +* +* Restrictions = None +* +* Input = None +* +* Output = None +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = None +* +*******************************************************************************/ +SQL_API_RC SQL_API_FN FreeToken +( + void *token, + char **errorMsg, + db2int32 *errorMsgLen +) +{ + IAM_TRACE_ENTRY("FreeToken"); + + *errorMsg = NULL; + *errorMsgLen = 0; + IAM_TRACE_EXIT("FreeToken",0); + + return DB2SEC_PLUGIN_OK; +} + + + +static SQL_API_RC getUsername( + const uid_t uid, + char * const userName, + db2int32 * const userNameLength, + char ** errorMessage) +{ + int err ; + SQL_API_RC rc = 0 ; + struct passwd * pResult ; + struct passwd passwordData = { 0 } ; + size_t bufSize = sysconf(_SC_GETPW_R_SIZE_MAX) ; + char * buf = malloc(bufSize) ; + IAM_TRACE_ENTRY("getUsername"); + + if (!buf) + { + char dumpMsg[256] =""; + snprintf(dumpMsg, sizeof(dumpMsg), "getUsername: Unable to allocate memory"); + *errorMessage = strdup(dumpMsg); + rc = DB2SEC_PLUGIN_UNEXPECTED_SYSTEM_ERROR; + goto exit; + } + + err = getpwuid_r( uid, + &passwordData, + buf, + bufSize, + &pResult ) ; + if (err) + { + char dumpMsg[256] =""; + snprintf(dumpMsg, sizeof(dumpMsg), "getUsername: Unable to find userid %d", uid); + *errorMessage = strdup(dumpMsg); + rc = DB2SEC_PLUGIN_BADUSER; + goto exit; + } + + *userNameLength = strlen(pResult->pw_name) ; + + if (*userNameLength > SQL_AUTH_IDENT) + { + char dumpMsg[256] =""; + snprintf(dumpMsg, sizeof(dumpMsg), "getUsername: Username is too long %s", pResult->pw_name); + *errorMessage = strdup(dumpMsg); + rc = DB2SEC_PLUGIN_BADUSER; + goto exit; + } + + strcpy(userName, pResult->pw_name) ; + +exit: + if(buf) + { + free(buf) ; + } + + IAM_TRACE_EXIT("getUsername",rc); + + return rc; +} + +/****************************************************************************** +* +* Function Name = GetDefaultLoginContext +* +* Descriptive Name = Determine the default user identity associated with +* the current process context. +* +* Function = If this function is called by Db2 client, +* no credentials have been provided, so always +* fail the connect. +* +* Dependencies = None +* +* Restrictions = None +* +* Input = None +* +* Output = None +* +* Normal Return = DB2SEC_PLUGIN_BADUSER +* +* Error Return = None +* +*******************************************************************************/ +SQL_API_RC SQL_API_FN GetDefaultLoginContext +( + char authID[], + db2int32 *authIDLength, + char userid[], + db2int32 *useridLength, + db2int32 useridType, + char domain[], + db2int32 *domainLength, + db2int32 *domainType, + const char *databaseName, + db2int32 databaseNameLength, + void **token, + char **errorMessage, + db2int32 *errorMessageLength +) +{ + int rc = DB2SEC_PLUGIN_OK; + int length; + char *user; + uid_t uid=-1; + struct passwd *pw = NULL; + + IAM_TRACE_ENTRY("GetDefaultLoginContext"); + + authID[0] = '\0'; + *authIDLength = 0; + userid[0] = '\0'; + *useridLength = 0; + domain[0] = '\0'; + *domainLength = 0; + *domainType = DB2SEC_USER_NAMESPACE_UNDEFINED; + + *errorMessage = NULL; + *errorMessageLength = 0; + + if(DB2SEC_PLUGIN_REAL_USER_NAME == useridType) + { + // Get the real username from the OS + uid = getuid (); + } + else if(DB2SEC_PLUGIN_EFFECTIVE_USER_NAME == useridType) + { + // Get the effective userid from the OS + uid = geteuid (); + } + else + { + char dumpMsg[256] =""; + snprintf(dumpMsg, sizeof(dumpMsg), "GetDefaultLoginContext: Invalid user type: %d", useridType); + *errorMessage = strdup(dumpMsg); + rc = DB2SEC_PLUGIN_UNEXPECTED_SYSTEM_ERROR; + goto exit; + } + + rc = getUsername(uid, userid, useridLength, errorMessage); + + if (DB2SEC_PLUGIN_OK == rc) + { + strcpy(authID, userid); + *authIDLength = *useridLength; + } + else + { + char dumpMsg[256] =""; + snprintf(dumpMsg, sizeof(dumpMsg), "GetDefaultLoginContext: Userid does not exist or is bad: %d", uid); + *errorMessage = strdup(dumpMsg); + rc = DB2SEC_PLUGIN_BADUSER; + } + +exit: + + if (*errorMessage != NULL) + { + *errorMessageLength = strlen(*errorMessage); + } + else + { + *errorMessageLength = 0; + } + + IAM_TRACE_EXIT("GetDefaultLoginContext",rc); + + return(rc); +} + +/****************************************************************************** +* +* Function Name = GenerateInitialCredUserPassword +* +* Descriptive Name = Generate the intial credentials based on the provided +* username/password pair and return the GSS-API +* credentials handle. +* +* Function = +* +* Dependencies = None +* +* Restrictions = - A forwardable TGT will always be requested +* - No change password functionality provided +* - If a REALM is specified with the username, then it will +* be included in the userid buffer and not parsed into +* the userNamespace field +* - Certain krb5 objects must be created and exist for the +* GSS-API cred handle to be valid. To make sure that +* these krb5 objects are cleaned up properly, their +* handles will be stored in a structure pointed to by +* pInitInfo so that they may be freed during +* db2secFreeInitInfo(). +* +* Input = userid - User name +* useridLen - Length of User name +* userNamespace - (not used) +* userNamespaceLen - (not used) +* userNamespaceType - (not used) +* password - User's password +* passwordLen - Password string length +* newPassword - (not used) +* newPasswordLen - (not used) +* dbName - (not used) +* dbnameLen - (not used) +* +* Output = pGSSCredHandle - Pointer to the GSS-API cred handle +* ppInitInfo - Pointer to a buffer allocated by the +* function to keep track of krb5 objects +* allocated to create the GSS-API cred handle +* ppErrorMsg - Pointer to a string that will contain +* an error message +* errorMsgLen - Pointer to the length of the error message +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = DB2SEC_PLUGIN_CHANGEPASSWORD_NOTSUPPORTED +* DB2SEC_PLUGIN_BAD_INPUT_PARAMETERS +* DB2SEC_PLUGIN_UNKNOWNERROR +* DB2SEC_PLUGIN_BADPWD +* DB2SEC_PLUGIN_BADUSER +* DB2SEC_PLUGIN_PWD_EXPIRED +* DB2SEC_PLUGIN_UID_EXPIRED +* +*******************************************************************************/ +SQL_API_RC SQL_API_FN GenerateInitialCredUserPassword +( + const char *userid, + db2int32 useridLen, + const char *usernamespace, + db2int32 usernamespacelen, + db2int32 usernamespacetype, + const char *password, + db2int32 passwordLen, + const char *newpassword, + db2int32 newpasswordLen, + const char *dbname, + db2int32 dbnameLen, + gss_cred_id_t *pGSSCredHandle, + void **ppInitInfo, + char **errorMsg, + db2int32 *errorMsgLen +) +{ + int rc = DB2SEC_PLUGIN_OK; + CRED_T *pCred = NULL; + char *localErrorMsg = NULL; + char oneNullByte[] = {'\0'}; + + IAM_TRACE_ENTRY("GenerateInitialCredUserPassword"); + + if (newpasswordLen > 0) + { + rc = DB2SEC_PLUGIN_CHANGEPASSWORD_NOTSUPPORTED; + goto exit; + } + + if (pGSSCredHandle == NULL) + { + localErrorMsg = "GenerateInitialCredential: pGSSCredHandle == NULL"; + rc = DB2SEC_PLUGIN_UNKNOWNERROR; + goto exit; + } + + /* Check lengths */ + if (useridLen > TOKEN_MAX_STRLEN) + { + localErrorMsg = "GenerateInitialCredential: userid too long"; + rc = DB2SEC_PLUGIN_BADUSER; + goto exit; + } + if (passwordLen > TOKEN_MAX_STRLEN) + { + localErrorMsg = "GenerateInitialCredential: password too long"; + rc = DB2SEC_PLUGIN_BADPWD; + goto exit; + } + + pCred = (CRED_T *)malloc(sizeof(CRED_T)); + if (pCred == NULL) + { + goto malloc_fail; + } + memset(pCred, '\0', sizeof(CRED_T)); + + /* Deal with NULL userids and passwords by using a one-byte + * string containing only a NULL. We flow this to the server + * and let it decide. + */ + if (useridLen == 0 || userid == NULL) + { + userid = oneNullByte; + useridLen = 1; + } + if (passwordLen == 0 || password == NULL) + { + password = oneNullByte; + passwordLen = 1; + } + + pCred->authtype = DB2SEC_AUTH_PASSWORD; + + pCred->useridLen = useridLen; + pCred->userid = (char *)malloc(useridLen); + if (pCred->userid == NULL) + { + goto malloc_fail; + } + memcpy(pCred->userid, userid, useridLen); + + pCred->authtokenLen = passwordLen; + pCred->authtoken = (char *)malloc(passwordLen); + if (pCred->authtoken == NULL) + { + goto malloc_fail; + } + memcpy(pCred->authtoken, password, passwordLen); + + *pGSSCredHandle = (gss_cred_id_t)pCred; + +exit: + + /* No init info */ + if (ppInitInfo != NULL) + { + *ppInitInfo = NULL; + } + + if (localErrorMsg != NULL) + { + *errorMsg = localErrorMsg; + *errorMsgLen = strlen(localErrorMsg); + } + else + { + *errorMsg = NULL; + *errorMsgLen = 0; + } + IAM_TRACE_EXIT("GenerateInitialCredUserPassword", rc); + + return(rc); + +malloc_fail: + if (pCred != NULL) + { + if (pCred->authtoken != NULL) free(pCred->authtoken); + if (pCred->userid != NULL) free(pCred->userid); + free(pCred); + } + + localErrorMsg = "GenerateInitialCredential: malloc failed"; + rc = DB2SEC_PLUGIN_NOMEM; + + goto exit; +} + +/* + * GenerateInitialCredAccessToken + */ +SQL_API_RC SQL_API_FN GenerateInitialCredAccessToken +( + const char *accesstoken, + db2int32 accesstokenLen, + const char *accesstokenspace, + db2int32 accesstokenspaceLen, + db2int32 accesstokenspaceType, + const char *dbname, + db2int32 dbnameLen, + gss_cred_id_t *pGSSCredHandle, + void **ppInitInfo, + char **errorMsg, + db2int32 *errorMsgLen +) +{ + int rc = DB2SEC_PLUGIN_OK; + CRED_T *pCred; + char *localErrorMsg = NULL; + char oneNullByte[] = {'\0'}; + const char *userid; + db2int32 useridLen; + + IAM_TRACE_ENTRY("GenerateInitialCredAccessToken"); + + if (pGSSCredHandle == NULL) + { + IAM_TRACE_DATA("GenerateInitialCredAccessToken","10"); + + localErrorMsg = "GenerateInitialCredAccessToken: pGSSCredHandle == NULL"; + rc = DB2SEC_PLUGIN_UNKNOWNERROR; + goto exit; + } + + /* Check lengths */ + if (accesstokenLen > TOKEN_MAX_AUTH_TOKEN_LEN) + { + IAM_TRACE_DATA("GenerateInitialCredAccessToken", "20"); + rc = DB2SEC_PLUGIN_BADPWD; + localErrorMsg = "GenerateInitialCredAccessToken: access token too long"; + goto exit; + } + + pCred = (CRED_T *)malloc(sizeof(CRED_T)); + if (pCred == NULL) + { + IAM_TRACE_DATA( "GenerateInitialCredAccessToken", "30"); + goto malloc_fail; + } + memset(pCred, '\0', sizeof(CRED_T)); + + /* Deal with NULL userids and passwords by using a one-byte + * string containing only a NULL. We flow this to the server + * and let it decide. + */ + + pCred->authtype = DB2SEC_AUTH_ACCESS_TOKEN; + + //pCred->useridLen = 0; + //pCred->userid = NULL; + userid = oneNullByte; + useridLen = 1; + pCred->useridLen = useridLen; + pCred->userid = (char *)malloc(useridLen); + if (pCred->userid == NULL) + { + IAM_TRACE_DATA( "GenerateInitialCredAccessToken","40"); + goto malloc_fail; + } + memcpy(pCred->userid, userid, useridLen); + + pCred->authtokenLen = accesstokenLen; + pCred->authtoken = (char *)malloc(accesstokenLen); + if (pCred->authtoken == NULL) + { + IAM_TRACE_DATA("GenerateInitialCredAccessToken", "50"); + goto malloc_fail; + } + memcpy(pCred->authtoken, accesstoken, accesstokenLen); + + *pGSSCredHandle = (gss_cred_id_t)pCred; + +exit: + + /* No init info */ + if (ppInitInfo != NULL) + { + *ppInitInfo = NULL; + } + + if (localErrorMsg != NULL) + { + *errorMsg = localErrorMsg; + *errorMsgLen = strlen(localErrorMsg); + } + else + { + *errorMsg = NULL; + *errorMsgLen = 0; + } + IAM_TRACE_EXIT("GenerateInitialCredAccessToken",rc); + + return(rc); + +malloc_fail: + if (pCred != NULL) + { + if (pCred->authtoken != NULL) free(pCred->authtoken); + if (pCred->userid != NULL) free(pCred->userid); + free(pCred); + } + + localErrorMsg = "GenerateInitialCredAccessToken: malloc failed"; + rc = DB2SEC_PLUGIN_NOMEM; + + goto exit; +} + + +/****************************************************************************** +* +* Function Name = ProcessServerPrincipalName +* +* Function = Process the principle name string returned from the +* server plugin and package it into a gss_name_t. +* +* Restrictions = None +* +* Input = name - Principal Name +* nameLen - Length of the Principal Name +* +* Output = gssName - Packaged Principal Name +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = DB2SEC_PLUGIN_BAD_PRINCIPAL_NAME +* +*******************************************************************************/ +SQL_API_RC SQL_API_FN ProcessServerPrincipalName +( + const char *name, + db2int32 nameLen, + gss_name_t *gssName, + char **errorMsg, + db2int32 *errorMsgLen +) +{ + int rc = DB2SEC_PLUGIN_OK; + NAME_T *pName; + IAM_TRACE_ENTRY("ProcessServerPrincipalName"); + + /* No error messages */ + *errorMsg = NULL; + *errorMsgLen = 0; + + if (name != NULL && nameLen > 0) + { + pName = (NAME_T *) malloc(sizeof(NAME_T)); + if (pName == NULL) + { + IAM_TRACE_DATA("ProcessServerPrincipalName","10"); + goto malloc_fail; + } + memset(pName, '\0', sizeof(NAME_T)); + + pName->useridLen = nameLen; + pName->userid = (char *) malloc(nameLen); + if (pName->userid == NULL) + { + IAM_TRACE_DATA("ProcessServerPrincipalName","20"); + goto malloc_fail; + } + memcpy(pName->userid, name, nameLen); + + *gssName = (gss_name_t)pName; + } + else + { + IAM_TRACE_DATA( "ProcessServerPrincipalName","30"); + rc = DB2SEC_PLUGIN_BAD_PRINCIPAL_NAME; + goto exit; + } + +exit: + IAM_TRACE_EXIT("ProcessServerPrincipalName",rc); + + return(rc); + +malloc_fail: + if (pName != NULL) + { + if (pName->userid) + { + free(pName->userid); + } + free(pName); + } + *errorMsg = "ProcessServerPrincipalName: malloc failed"; + *errorMsgLen = strlen(*errorMsg); + goto exit; +} + +/* FreeInitInfo() + * A no-op, since we don't set up any init info. + */ +/****************************************************************************** +* +* Function Name = FreeInitInfo +* +* Descriptive Name = Free Memory used for the init info +* +* Function = We don't return an allocated init info, so nothing to +* free +* +* Dependencies = None +* +* Restrictions = None +* +* Input = None +* +* Output = None +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = None +* +*******************************************************************************/ +SQL_API_RC SQL_API_FN FreeInitInfo +( + void *initInfo, + char **errorMsg, + db2int32 *errorMsgLen +) +{ + int rc = DB2SEC_PLUGIN_OK; + + IAM_TRACE_ENTRY("FreeInitInfo"); + + *errorMsg = NULL; + *errorMsgLen = 0; + + if( initInfo != NULL ) + { + // We don't expect this to be allocated + rc = DB2SEC_PLUGIN_UNEXPECTED_SYSTEM_ERROR; + *errorMsg = "FreeInitInfo: initInfo != NULL"; + *errorMsgLen = strlen(*errorMsg); + goto exit; + } + +exit: + IAM_TRACE_EXIT("FreeInitInfo",rc); + + return(rc); +} + +/****************************************************************************** +* +* Function Name = gss_init_sec_context +* +* Function = Builds a context based on the input credentials +* Db2 client sends a payload to Db2 server based on this +* context. +* +* Dependencies = None +* +* Restrictions = None +* +* Input = cred_handle - input credentials that we will massage +* into the format understood by the +* server-side security plugin +* target_name - name of the server-side security plugin +* input_token - not used +* mech_type - not used +* req_flags - not used +* time_req - not used +* +* Output = context_handle - the security context that is being +* constructed +* output_token - the constructed data block with +* credentials in a format understood +* by the server +* minor_status - error message code from this plugin +* action_mech_type - not used +* ret_flags - not used +* time_rec - not used +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = DB2SEC_PLUGIN_INCOMPATIBLE_VER +* +*******************************************************************************/ +OM_uint32 SQL_API_FN gss_init_sec_context +( + OM_uint32 * minor_status, + const gss_cred_id_t cred_handle, + gss_ctx_id_t * context_handle, + const gss_name_t target_name, + const gss_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + const gss_buffer_t input_token, + gss_OID * actual_mech_type, + gss_buffer_t output_token, + OM_uint32 * ret_flags, + OM_uint32 * time_rec +) +{ + OM_uint32 rc = GSS_S_COMPLETE; + NAME_T *pTarget = NULL; + NAME_T localTarget; + CONTEXT_T *pCtx = NULL; + CRED_T *pCred = NULL; + CRED_T defaultCred; + AUTHINFO_T *pOutToken = NULL; + char *localuser = NULL; + char *localpswd = NULL; + char *cptr = NULL; + char *errMsg = NULL; + int length; + IAM_TRACE_ENTRY("gss_init_sec_context"); + + /* Check for unsupported options */ + if (context_handle == NULL) + { + IAM_TRACE_DATA("gss_init_sec_context","10"); + + errMsg = "gss_init_sec_context: context_handle == NULL"; + rc = GSS_S_NO_CONTEXT; + goto exit; + } + + /* Check the target name to be the plugin name */ + pTarget = (NAME_T *)target_name; + if (pTarget == GSS_C_NO_NAME) + { + localTarget.userid = PRINCIPAL_NAME; + localTarget.useridLen = strlen(PRINCIPAL_NAME); + pTarget = &localTarget; + } + else if(strncmp(pTarget->userid, PRINCIPAL_NAME, pTarget->useridLen) != 0) + { + IAM_TRACE_DATA("gss_init_sec_context","20"); + errMsg = "gss_init_sec_context: Principle name mismatch"; + rc = GSS_S_BAD_NAME; + goto exit; + } + + /* Check the input credentials */ + if (cred_handle == GSS_C_NO_CREDENTIAL) + { + IAM_TRACE_DATA("gss_init_sec_context","30"); + errMsg = "gss_init_sec_context: No credentials"; + rc = GSS_S_BAD_NAME; + goto exit; + } + else + { + pCred = (CRED_T *)cred_handle; + } + + /* On first call to init_sec_context, the context handle should be set to */ + /* GSS_C_NO_CONTEXT; set up the context structure */ + if (*context_handle == GSS_C_NO_CONTEXT) + { + pCtx = (CONTEXT_T *)malloc(sizeof(CONTEXT_T)); + if (pCtx == NULL) + { + IAM_TRACE_DATA("gss_init_sec_context","40"); + goto malloc_fail; + } + memset(pCtx, '\0', sizeof(CONTEXT_T)); + + pCtx->targetLen = pTarget->useridLen; + pCtx->target = (char *)malloc(pCtx->targetLen); + if (pCtx->target == NULL) + { + IAM_TRACE_DATA("gss_init_sec_context","50"); + goto malloc_fail; + } + memcpy(pCtx->target, pTarget->userid, pCtx->targetLen); + + // No source is needed + pCtx->sourceLen = 0; + pCtx->source = NULL; + + pCtx->ctxCount = 0; + *context_handle = pCtx; + } + else + { + pCtx = (CONTEXT_T *)*context_handle; + if (pCtx->ctxCount == 0) + { + IAM_TRACE_DATA( "gss_init_sec_context","60"); + errMsg = "gss_init_sec_context: Invalid context handle"; + rc = GSS_S_NO_CONTEXT; + goto exit; + } + } + + // First invocation + // Create the data block (output token) that contains the credentials + // for the server-side plugin to process + if (pCtx->ctxCount == 0) + { + /* There should be no input token */ + if (input_token != GSS_C_NO_BUFFER) + { + IAM_TRACE_DATA("gss_init_sec_context","70"); + errMsg = "gss_init_sec_context: bad input_token"; + rc = GSS_S_FAILURE; + *minor_status = RETCODE_BADTOKEN; + goto exit; + } + + int tokenLen = sizeof(AUTHINFO_T) + - sizeof( char[1] ) // char data[1] + + pCred->authtokenLen + + pCred->useridLen; + + pOutToken = (AUTHINFO_T *)malloc( tokenLen ); + if (pOutToken == NULL) + { + IAM_TRACE_DATA("gss_init_sec_context","80"); + goto malloc_fail; + } + + memset(pOutToken, '\0', tokenLen); + + pOutToken->version = (OM_uint32) ByteReverse(AUTHINFO_VERSION_1); + pOutToken->authType = (OM_uint32) ByteReverse(pCred->authtype); + pOutToken->authTokenLen = (OM_uint32) ByteReverse(pCred->authtokenLen); + pOutToken->useridLen = (OM_uint32) ByteReverse(pCred->useridLen); + + if( pCred->authtokenLen > 0 ) + { + memcpy(&pOutToken->data[0], pCred->authtoken, pCred->authtokenLen); + } + + if( pCred->useridLen > 0 ) + { + memcpy(&pOutToken->data[pCred->authtokenLen], pCred->userid, pCred->useridLen); + } + + output_token->value = (void *)pOutToken; + output_token->length = tokenLen; + + if (req_flags & GSS_C_MUTUAL_FLAG) + { + IAM_TRACE_DATA("gss_init_sec_context","90"); + + /* Mutual authentication requested */ + rc = GSS_S_CONTINUE_NEEDED; + } + else + { + /* Make the context count negative so that the next invocation will */ + /* result in an error */ + pCtx->ctxCount = -2; + } + } + else if (pCtx->ctxCount == 1) + { + /* Set the context count to negative so that the next invocation will */ + /* result in an error */ + pCtx->ctxCount = -2; + } + else + { + /* Function shouldn't have been called again for context establishment */ + IAM_TRACE_DATA("gss_init_sec_context", "100"); + errMsg = "context count too high!"; + rc = GSS_S_FAILURE; + *minor_status = RETCODE_PROGERR; + goto exit; + } + + // Fill in secondary information + if(actual_mech_type != NULL) + { + *actual_mech_type = mech_type; + } + if(ret_flags != NULL) + { + *ret_flags = req_flags; + } + if(time_rec != NULL) + { + *time_rec = time_req; + } + +exit: + if (errMsg != NULL) + { + char msg[512]; + sprintf(msg,"AWSIAMauth::gss_init_sec_context error: %s", errMsg); + logFunc(DB2SEC_LOG_ERROR, msg, strlen(msg)); + } + if(pCtx != NULL) + (pCtx->ctxCount)++; + + IAM_TRACE_EXIT("gss_init_sec_context",rc); + + return(rc); + +malloc_fail: + if (localuser != NULL) + { + free(localuser); + } + if (localpswd != NULL) + { + free(localpswd); + } + if (pCtx != NULL) + { + if (pCtx->target != NULL) + { + free(pCtx->target); + } + if (pCtx->source != NULL) + { + free(pCtx->source); + } + free(pCtx); + } + if (pOutToken != NULL) + { + free(pOutToken); + } + + rc = GSS_S_FAILURE; + *minor_status = RETCODE_MALLOC; + + logFunc(DB2SEC_LOG_ERROR, + "AWSIAMauth::gss_init_sec_context: malloc failure", 50); + + IAM_TRACE_EXIT("gss_init_sec_context",rc); + return(rc); +} + +/****************************************************************************** +* +* Function Name = db2secClientAuthPluginInit +* +* Function = Set up plugin function pointers and +* perform other initialization. +* This function is called by name when the plugin is +* loaded so this function name is the same for all +* security plugins. +* +* Dependencies = None +* +* Restrictions = None +* +* Input = version - Version of the plugin that Db2 can handle +* msgFunc - function pointer for writing messages back to +* Db2 +* +* Output = None +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = DB2SEC_PLUGIN_INCOMPATIBLE_VER +* +*******************************************************************************/ +SQL_API_RC SQL_API_FN db2secClientAuthPluginInit +( + db2int32 version, + void *fns, + db2secLogMessage *msgFunc, + char **errormsg, + db2int32 *errormsglen +) +{ + int rc = DB2SEC_PLUGIN_OK; + db2secGssapiClientAuthFunctions_2 *pFPs; + IAM_TRACE_ENTRY("db2secClientAuthPluginInit"); + + /* No error message */ + *errormsg = NULL; + *errormsglen = 0; + + /* Written to version 2 of the API */ + if (version < DB2SEC_GSSAPI_CLIENT_AUTH_FUNCTIONS_VERSION_2) + { + rc = DB2SEC_PLUGIN_INCOMPATIBLE_VER; + goto exit; + } + + pFPs = (db2secGssapiClientAuthFunctions_2 *) fns; + + pFPs->plugintype = DB2SEC_PLUGIN_TYPE_GSSAPI; + pFPs->version = DB2SEC_GSSAPI_CLIENT_AUTH_FUNCTIONS_VERSION_2; + + /* Set up function pointers */ + pFPs->db2secGetDefaultLoginContext = GetDefaultLoginContext; + pFPs->db2secGenerateInitialCred = GenerateInitialCredUserPassword; + pFPs->db2secGenerateInitialCredAccessToken = GenerateInitialCredAccessToken; + pFPs->db2secProcessServerPrincipalName = ProcessServerPrincipalName; + pFPs->db2secFreeToken = FreeToken; + pFPs->db2secFreeInitInfo = FreeInitInfo; + pFPs->gss_init_sec_context = gss_init_sec_context; + + /* From AWSIAMauthcommon.c */ + pFPs->gss_delete_sec_context = gss_delete_sec_context; + pFPs->db2secClientAuthPluginTerm = PluginTerminate; + pFPs->db2secFreeErrormsg = FreeErrorMessage; + pFPs->gss_display_status = gss_display_status; + pFPs->gss_release_buffer = gss_release_buffer; + pFPs->gss_release_cred = gss_release_cred; + pFPs->gss_release_name = gss_release_name; + + logFunc = msgFunc; + +exit: + + IAM_TRACE_EXIT("db2secClientAuthPluginInit",rc); + + return(rc); +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthcommon.c b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthcommon.c new file mode 100644 index 0000000..96fa7f1 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthcommon.c @@ -0,0 +1,492 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +****************************************************************************** +* +** Source File Name = src/gss/AWSIAMauthcommon.c +** +** Descriptive Name = Common Code for IAM authentication plugin +** +** Function: Provide common functions shared between client-side and +** server-side security plugins +** +** +** +*******************************************************************************/ + +#include "AWSIAMauth.h" +#include "../common/AWSIAMtrace.h" + +/****************************************************************************** +* +* Function Name = ByteReverse +* +* Function = Reverse the given integer for endianess +* This plugin sends a AUTHINFO_T (defined above) between +* client and server system, which may be of different +* endianess. +* The length fields in the AUTHINFO_T are sent in Network +* Byte Order (big endian), and this function is used to +* convert them when required. +* +* Rather than depend on a static #define to determine +* behavior we determine the endianess of the current +* system on the fly. +* +* Dependencies = None +* +* Restrictions = None +* +* Input = None +* +* Output = None +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = None +* +*******************************************************************************/ +int ByteReverse( int input ) +{ + int output = input; + union + { + short s; + char c; + } test; + + test.s = 1; + if (test.c == (char)1) + { + /* This is a little endian platform, byte reverse. + * We try to make no assumptions about the size of the native + * type here. This may not be efficient, but it's portable. + */ + char *ip = (char*)&input; + char *op = (char*)&output; + int size = sizeof(int); + int i; + for (i=0; i < size; i++) + { + op[i] = ip[size - i - 1]; + } + } + return(output); +} + +/****************************************************************************** +* +* Function Name = FreeErrorMessage +* +* Function = This is no-op. All error messaged returned by this +* plugin are static C strings. +* +* Dependencies = None +* +* Restrictions = None +* +* Input = errormsg - error message to be freed +* +* Output = None +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = None +* +*******************************************************************************/ +SQL_API_RC SQL_API_FN FreeErrorMessage( char *errormsg ) +{ + if (errormsg != NULL) free(errormsg); + return(DB2SEC_PLUGIN_OK); +} + +/****************************************************************************** +* +* Function Name = gss_release_cred() +* +* Function = Free the specified credential and free associated memory +* (gss_cred_id_t) +* +* Dependencies = None +* +* Restrictions = None +* +* Input = pCredHandle - credential handle to be freed +* +* Output = None +* +* Normal Return = GSS_S_COMPLETE +* +* Error Return = None +* +*******************************************************************************/ +OM_uint32 SQL_API_FN gss_release_cred +( + OM_uint32 *minorStatus, + gss_cred_id_t *pCredHandle +) +{ + OM_uint32 rc = GSS_S_COMPLETE; + CRED_T *pCred; + IAM_TRACE_ENTRY("gss_release_cred"); + + // This condition also accounts for pCredHandle == GSS_C_NO_CREDENTIAL + if (pCredHandle != NULL) + { + if (*pCredHandle != GSS_C_NO_CREDENTIAL) + { + pCred = (CRED_T *) *pCredHandle; + free( pCred->userid ); + free( pCred->authtoken ); + free( pCred ); + *pCredHandle = GSS_C_NO_CREDENTIAL; + } + } + else + { + rc = GSS_S_NO_CRED; + goto exit; + } + +exit: + IAM_TRACE_EXIT("gss_release_cred",0); + return(rc); +} + +/****************************************************************************** +* +* Function Name = gss_release_name() +* +* Function = Free the memory used by gss_name_t in this plugin +* +* Dependencies = None +* +* Restrictions = None +* +* Input = name - name to be freed +* +* Output = minorStatus - not used +* +* Normal Return = GSS_S_COMPLETE +* +* Error Return = None +* +*******************************************************************************/ +OM_uint32 SQL_API_FN gss_release_name +( + OM_uint32 *minorStatus, + gss_name_t *name +) +{ + OM_uint32 rc = GSS_S_COMPLETE; + NAME_T *pName; + IAM_TRACE_ENTRY("gss_release_name"); + + if (name != NULL && *name != NULL) + { + pName = (NAME_T *) *name; + free(pName->userid); + free(pName); + *name = GSS_C_NO_NAME; + } + IAM_TRACE_EXIT("gss_release_name",0); + + return(rc); +} + +/* gss_release_buffer() + * Free the specified buffer. + */ +/****************************************************************************** +* +* Function Name = gss_release_buffer() +* +* Function = Free the buffer (gss_release_t) passed to Db2 before +* +* Dependencies = None +* +* Restrictions = None +* +* Input = buffer - buffer to be freed +* +* Output = minorStatus - not used +* +* Normal Return = GSS_S_COMPLETE +* +* Error Return = None +* +*******************************************************************************/ +OM_uint32 SQL_API_FN gss_release_buffer +( + OM_uint32 *minorStatus, + gss_buffer_t buffer +) +{ + OM_uint32 rc = GSS_S_COMPLETE; + IAM_TRACE_ENTRY("gss_release_buffer"); + + if( (buffer != NULL) && + (buffer->length > 0) && + (buffer->value != NULL) ) + { + free(buffer->value); + buffer->value = NULL; + buffer->length = 0; + } + + + IAM_TRACE_EXIT("gss_release_buffer",rc); + + return(rc); +} + +/****************************************************************************** +* +* Function Name = delete_context +* +* Function = Free the memory used by the given context +* +* Dependencies = None +* +* Restrictions = None +* +* Input = pCtx - context to be freed +* +* Output = None +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = None +* +*******************************************************************************/ +void delete_context( CONTEXT_T *pCtx ) +{ + + IAM_TRACE_ENTRY("delete_context"); + + if (pCtx != NULL) + { + if (pCtx->target != NULL) + { + free(pCtx->target); + } + if (pCtx->source != NULL) + { + free(pCtx->source); + } + if( pCtx->groups != NULL ) + { + int i; + for( i = 0; i < pCtx->groupCount; ++i ) + { + if( pCtx->groups[i].group_name ) free( pCtx->groups[i].group_name ); + } + } + free(pCtx); + } + IAM_TRACE_EXIT("delete_context", 0); + +} + +/****************************************************************************** +* +* Function Name = gss_delete_sec_context +* +* Function = Free the specified context +* +* Dependencies = None +* +* Restrictions = None +* +* Input = context_handle - context to be freed +* +* Output = None +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = None +* +*******************************************************************************/ +OM_uint32 SQL_API_FN gss_delete_sec_context +( + OM_uint32 *minorStatus, + gss_ctx_id_t *context_handle, + gss_buffer_t output_token +) +{ + OM_uint32 rc=GSS_S_COMPLETE; + CONTEXT_T *pCtx; + + IAM_TRACE_ENTRY("gss_delete_sec_context"); + + if (context_handle != NULL && *context_handle != NULL) + { + pCtx = (CONTEXT_T *)*context_handle; + delete_context(pCtx); + *context_handle = GSS_C_NO_CONTEXT; + + if (output_token != GSS_C_NO_BUFFER) + { + output_token->value = NULL; + output_token->length = 0; + } + } + else + { + rc = GSS_S_NO_CONTEXT; + goto exit; + } + +exit: + IAM_TRACE_EXIT("gss_delete_sec_context", rc); + + return(rc); +} + +/****************************************************************************** +* +* Function Name = gss_display_status() +* +* Function = Return the text message associated with the given status +* type and value +* +* Dependencies = None +* +* Restrictions = None +* +* Input = status_value - status value +* status_type - status type +* mech_type - not used +* +* Output = None +* +* Normal Return = GSS_S_COMPLETE +* +* Error Return = None +* +*******************************************************************************/ +OM_uint32 SQL_API_FN gss_display_status +( + OM_uint32 *minor_status, + OM_uint32 status_value, + int status_type, + const gss_OID mech_type, + OM_uint32 *message_context, + gss_buffer_t status_string +) +{ + OM_uint32 rc=GSS_S_COMPLETE; + IAM_TRACE_ENTRY("gss_display_status"); + + /* No mech types supported */ + if (mech_type != NULL) + { + rc = GSS_S_BAD_MECH; + goto exit; + } + + /* Regardless of the type of status code, a 0 means success */ + if (status_value == GSS_S_COMPLETE) + { + status_string->length = strlen(retcodeMessage[RETCODE_OK]) + 1; + status_string->value = (void *) malloc(status_string->length); + if (status_string->value == NULL) + { + goto malloc_fail; + } + strcpy((char *)(status_string->value), retcodeMessage[RETCODE_OK]); + goto exit; + } + + if (status_type == GSS_C_GSS_CODE) + { + /* Major status code -- we only have 1 for the moment */ + status_string->length = strlen(MAJOR_CODE_STRING) + 1; + status_string->value = (void *)malloc(status_string->length); + if (status_string->value == NULL) + { + goto malloc_fail; + } + strcpy((char *)(status_string->value), MAJOR_CODE_STRING); + } + else if (status_type == GSS_C_MECH_CODE) + { + // Minor status code + // Make sure that the status value is within range + if (status_value > RETCODE_MAXCODE) + { + rc = GSS_S_BAD_STATUS; + *minor_status = RETCODE_UNKNOWN; + goto exit; + } + status_string->length = strlen(retcodeMessage[status_value]) + 1; + status_string->value = (void *)malloc(status_string->length); + if (status_string->value == NULL) + { + goto malloc_fail; + } + strcpy((char *)(status_string->value), retcodeMessage[status_value]); + } + else + { + rc = GSS_S_BAD_STATUS; + goto exit; + } + +exit: + /* No more messages available */ + *message_context = 0; + IAM_TRACE_EXIT("gss_display_status",rc); + + return(rc); + +malloc_fail: + status_string->length = 0; + rc = GSS_S_FAILURE; + *minor_status = RETCODE_MALLOC; + goto exit; +} + +/****************************************************************************** +* +* Function Name = PluginTerminate() +* +* Function = Clean up anything allocated during plugin initialization +* +* Dependencies = None +* +* Restrictions = None +* +* Input = None +* +* Output = None +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = None +* +*******************************************************************************/ +SQL_API_RC SQL_API_FN PluginTerminate +( + char **errorMsg, + db2int32 *errorMsgLen +) +{ + // Nothing to do + IAM_TRACE_ENTRY("PluginTerminate"); + + *errorMsg = NULL; + *errorMsgLen = 0; + IAM_TRACE_EXIT("PluginTerminate", 0); + + return(DB2SEC_PLUGIN_OK); +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthfile.h b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthfile.h new file mode 100644 index 0000000..9557acb --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthfile.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +****************************************************************************** +** +** Source File Name = src/gss/AWSIAMauthfile.h +** +** Descriptive Name = FILE utility functions header file +** +** Function: Functions for communicating with file +** +** +** +*******************************************************************************/ + +#ifndef _AWS_IAM_AUTHFILE_H +#define _AWS_IAM_AUTHFILE_H + +#include +#include +#include +#define URL_BUFFER_SIZE 1024 +#define MAX_ERROR_MSG_SIZE 2048 + +#ifdef SQLUNIX + #include + #include +#else + #define strcasecmp(a,b) stricmp(a,b) + #define snprintf _snprintf + #define vsnprintf _vsnprintf +#endif + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifdef __cplusplus +#define DB2FILE_EXT_C extern "C" +#else +#define DB2FILE_EXT_C +#endif + +#define SALT_SZ 4 +#define MAX_SALT_SZ 40 +#define MAX_ENCODED_PASSWORD_HASH 256 + +#ifndef IS_EMPTY + #define IS_EMPTY(x) (x == NULL || x[0] == '\0') +#endif +#endif // _AWS_IAM_AUTHFILE_H diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthgroup.c b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthgroup.c new file mode 100644 index 0000000..b88494b --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthgroup.c @@ -0,0 +1,526 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +****************************************************************************** +** +** Source File Name = src/gss/AWSIAMauthgroup.c +** +** Descriptive Name = Plugin that queries AWS cognito for user groups and users.json for system's users +** +** Function: Implements functions required by Db2 group plugin architeture +** This plugin is meant to be used with AWS IAM security plugin. +** +** With AWS IAM security plugin in place, the authentication will be +** done against IAM, and not through the OS. Assuming the +** authentication is successful, the next step is to determine +** authorization. For AWS IAM user, all the groups must be collected from AWS cognito. +** The collected group names are aggregated to +** form the full list. In case of OS based authentication, users.json +** will be looked at to get the groups of default system user like db2inst1. +** +** +** +** +*******************************************************************************/ + +#include +#include +#include "AWSIAMauthfile.h" +#include "../common/usersjson.h" +#include +#include +#include +#include +#include +#include +#include +#include "AWSIAMauth.h" +#include "AWSIAMauthfile.h" +#include "../common/AWSIAMtrace.h" +#include "iam.h" +#include "AWSUserGroupInfo.h" +#include +#include "utils.h" + +db2secLogMessage *db2LogFunc = NULL; +#define JSON_READ_ERROR "Error reading json objects from %s" + +/* Types used to manage the linked list of groups that + * is build during the group lookup process. + */ +typedef struct groupElem_s { + struct groupElem_s *next; + char name[1]; /* variable size */ +} groupElem; + +typedef struct { + groupElem *first; + groupElem *last; +} groupListHead; + + +/* addGroupToList + * + * Add "name" to the list anchored by "head". + * + * Returns: 0 (success) or DB2SEC_PLUGIN_NOMEM. + */ +static int addGroupToList(groupListHead *head, + const char *name) +{ + int rc = DB2SEC_PLUGIN_OK; + groupElem *newgrp; + + IAM_TRACE_ENTRY("addGroupToList"); + newgrp = (groupElem*)malloc(sizeof(groupElem) + strlen(name)); + if (newgrp != NULL) + { + strcpy(newgrp->name, name); + newgrp->next = NULL; + + if (head->last == NULL) + { + head->first = newgrp; + head->last = newgrp; + } + else + { + head->last->next = newgrp; + head->last = newgrp; + } + } + else + { + rc = DB2SEC_PLUGIN_NOMEM; + } + + return(rc); +} + +/* freeList + * + * Free the memory associated with the list anchored by "head". + */ +static void freeList(groupListHead *head) +{ + groupElem *cur, *next; + + IAM_TRACE_ENTRY("freeList"); + + if (head != NULL) + { + cur = head->first; + while (cur != NULL) + { + next = cur->next; + free(cur); + cur = next; + } + + head->first = NULL; + } + + IAM_TRACE_EXIT("freeList", 0); + return; +} + + + +/* FindGroups + * + * Find the groups memberships associated with the input AuthID. + * + * <= authID - this will be the authorization ID and it is always uppercase + * + */ +SQL_API_RC SQL_API_FN FindGroups(const char *authID, + db2int32 authIDLength, + const char *userID, /* ignored */ + db2int32 userIDLength, /* ignored */ + const char *domain, /* ignored */ + db2int32 domainLength, /* ignored */ + db2int32 domainType, /* ignored */ + const char *databaseName, /* ignored */ + db2int32 databaseNameLength, /* ignored */ + void *token, + db2int32 tokenType, + db2int32 location, + const char *authPluginName, /* ignored */ + db2int32 authPluginNameLength, /* ignored */ + void **groupList, + db2int32 *groupCount, + char **errorMessage, + db2int32 *errorMessageLength) +{ + int rc = DB2SEC_PLUGIN_OK; + int totalLen=0, len=0, num=0; + char local_authid[DB2SEC_MAX_AUTHID_LENGTH + 1] = ""; + char dumpMsg[MAX_ERROR_MSG_SIZE] = ""; + bool inUsersJson = TRUE; + const char *groupListG = NULL; + groupElem *cur = NULL; + groupListHead grpList, grpList2; + unsigned char *ucp = NULL; + struct json_object *parsed_json = NULL; + CONTEXT_T* pCtxt = (CONTEXT_T*)token; + int i; + struct json_object *passwordt = NULL, *user_json = NULL, *user = NULL, *group = NULL; + char *gname = NULL; + struct timespec ts; + + *errorMessage = NULL; + *errorMessageLength = 0; + + IAM_TRACE_ENTRY("FindGroups"); + + memset(&grpList, 0, sizeof(grpList)); + memset(&grpList2, 0, sizeof(grpList2)); + if (authIDLength > DB2SEC_MAX_AUTHID_LENGTH) + { + strncpy(local_authid, authID, DB2SEC_MAX_AUTHID_LENGTH); + local_authid[DB2SEC_MAX_AUTHID_LENGTH] = '\0'; + snprintf(dumpMsg, sizeof(dumpMsg), "FindGroups: " + "AuthID is too long (%d bytes)\n[Truncated]:%s", + (int)authIDLength, local_authid); + *errorMessage = strdup(dumpMsg); + rc = DB2SEC_PLUGIN_BADUSER; + goto exit; + } + + memcpy(local_authid, authID, authIDLength); + local_authid[authIDLength] = '\0'; + + IAM_TRACE_DATA("FindGroups, local_authid:", local_authid); + + if (authID == NULL || groupList == NULL || groupCount == NULL) + { + *errorMessage = strdup("FindGroups: invalid arguments"); + rc = DB2SEC_PLUGIN_BAD_INPUT_PARAMETERS; + goto exit; + } + IAM_TRACE_DATA("FindGroups", "Started collecting token groups from context"); + totalLen = 0; + + if (tokenType == DB2SEC_GENERIC) + { + // Check for system user + // open the user registry file + char json_err[256]; + parsed_json = json_object_from_file(DB2OC_USER_REGISTRY_FILE); + + for(int retries = 0; retries < 5 && parsed_json == NULL; retries++) + { +#ifdef JSON_C_0_13 + const char* json_e = json_util_get_last_err(); + strcpy(json_err, json_e); +#else + snprintf(json_err, sizeof(json_err), JSON_READ_ERROR , DB2OC_USER_REGISTRY_FILE); +#endif + UsersJsonErrorMsg("FindGroups", errorMessage, errorMessageLength, json_err); + db2LogFunc(DB2SEC_LOG_ERROR, *errorMessage, *errorMessageLength); + free(*errorMessage); + *errorMessage = NULL; + ts.tv_sec = 0; + ts.tv_nsec = 100000000; + nanosleep(&ts, NULL); // sleep for 100 ms + parsed_json = json_object_from_file(DB2OC_USER_REGISTRY_FILE); + } + + if(parsed_json == NULL) + { +#ifdef JSON_C_0_13 + const char* json_e = json_util_get_last_err(); + strcpy(json_err, json_e); +#else + snprintf(json_err, sizeof(json_err), JSON_READ_ERROR , DB2OC_USER_REGISTRY_FILE); +#endif + UsersJsonErrorMsg("FindGroups", errorMessage, errorMessageLength, json_err); + rc = DB2SEC_PLUGIN_BADUSER; + DumpUsersJson(DB2OC_USER_REGISTRY_FILE, db2LogFunc); + goto exit; + } + + json_object_object_get_ex(parsed_json, "users", &user_json); + + // Authorization IDs are upper case. The user file can be lower case + // Convert it to lower if we can't find it the first time. + if(!json_object_object_get_ex(user_json, local_authid, &user)) + { + stringToLower(local_authid); + if(!json_object_object_get_ex(user_json, local_authid, &user)) inUsersJson = FALSE; + } + + len = snprintf(dumpMsg, sizeof(dumpMsg), "FindGroups: found in users %d ", + inUsersJson); + db2LogFunc(DB2SEC_LOG_INFO, dumpMsg, len); + + if(inUsersJson) + { + json_object_object_get_ex(user, "group", &group); + + groupListG = json_object_get_string(group); + + if (groupListG == NULL) + { + len = snprintf(dumpMsg, sizeof(dumpMsg), "FindGroups: no groups found for users %s ", + local_authid); + db2LogFunc(DB2SEC_LOG_INFO, dumpMsg, len); + IAM_TRACE_DATA("findGroups", "is NULL"); + gname = NULL; + } else { + IAM_TRACE_DATA("findGroups", "is NOT NULL"); + // We have the group list if it exists - parse it and add the groups to the list + gname = strtok ((char*)groupListG,","); + len = snprintf(dumpMsg, sizeof(dumpMsg), "FindGroups: groups found for users %s: %s ", + local_authid, gname); + db2LogFunc(DB2SEC_LOG_INFO, dumpMsg, len); + } + + while (gname != NULL) + { + len = strlen(gname); + if (len <= DB2SEC_MAX_AUTHID_LENGTH) + { + rc = addGroupToList(&grpList2, gname); + if (rc != DB2SEC_PLUGIN_OK) + { + snprintf(dumpMsg, sizeof(dumpMsg), + "FindGroups: addGroupToList " + "rc=%d for group '%s'\n", rc, gname); + *errorMessage = strdup(dumpMsg); + goto exit; + } + + gname = strtok (NULL, ","); + totalLen += len +2; /* length of gname + 1 byte + NULL */ + } + } + + if(totalLen > 0) + goto populate; + rc = DB2SEC_PLUGIN_USERSTATUSNOTKNOWN; + goto exit; + } + else + { + len = snprintf(dumpMsg, sizeof(dumpMsg), "FindGroups: no group found for user %s in users.json", + local_authid); + db2LogFunc(DB2SEC_LOG_INFO, dumpMsg, len); + } + } + + if (tokenType == DB2SEC_GSSAPI_CTX_HANDLE) + { + const char* userPoolID = read_userpool_from_cfg(); + AWS_USER_GROUPS_T* awsusergroups = NULL; + rc = FetchAWSUserGroups(local_authid, userPoolID, &awsusergroups); + if( rc == 0 ) + { + len = snprintf(dumpMsg, sizeof(dumpMsg), "FindGroups: groups found for users from AWS %s: %d ", + local_authid, awsusergroups->groupCount); + db2LogFunc(DB2SEC_LOG_INFO, dumpMsg, len); + if(awsusergroups->groupCount < 100){ + /* If the group info is recieved from the token, + * populate the group list and return the list + * directly to DB2. + */ + for( i = 0; i < awsusergroups->groupCount; ++i ) + { + printf("Groups from AWS: %s", awsusergroups->groups[i].group_name); + rc = addGroupToList(&grpList2, awsusergroups->groups[i].group_name ); + if (rc != DB2SEC_PLUGIN_OK) + { + snprintf(dumpMsg, sizeof(dumpMsg), + "FindGroups: addGroupToList " + "rc=%d for AWS cognito groups \n", rc); + *errorMessage = strdup(dumpMsg); + goto exit; + } + totalLen += awsusergroups->groups[i].groupNameLen + 2; /* length byte + NULL byte */ + } + IAM_TRACE_DATA("FindGroups", "Finished collecting token groups from context"); + goto populate; + } + } + else + { + len = snprintf(dumpMsg, sizeof(dumpMsg), "FindGroups: no group found for user %s either from users.json or from AWS ", + local_authid); + db2LogFunc(DB2SEC_LOG_INFO, dumpMsg, len); + } + } + /* If we found no groups for the user, return now. */ + if (grpList.first == NULL) + { + *groupList = NULL; + *groupCount = 0; + + rc = DB2SEC_PLUGIN_OK; + goto exit; + } + + /* It's possible that we have no group information here, + * if we couldn't find names for any of the group DNs. + */ + +populate: + /* It's possible that we have no group information here, + * if we couldn't find any roles in Azure AD. + */ + if ( totalLen <= 0 ) + { + strncpy(local_authid, authID, DB2SEC_MAX_AUTHID_LENGTH); + local_authid[DB2SEC_MAX_AUTHID_LENGTH] = '\0'; + len = snprintf(dumpMsg, sizeof(dumpMsg), "FindGroups: " + "No db2 groups for authid='%s' \n", + local_authid ); + db2LogFunc(DB2SEC_LOG_INFO, dumpMsg, len); + + *groupList = NULL; + *groupCount = 0; + rc = DB2SEC_PLUGIN_OK; + goto exit; + } + + /* Now malloc & populate the group list that will be returned to DB2. */ + *groupList = malloc(totalLen); + if (*groupList == NULL) + { + *errorMessage = strdup("FindGroups: malloc failure"); + rc = DB2SEC_PLUGIN_NOMEM; + goto exit; + } + + *groupCount = 0; + + // Copy the group list to the Memory allocated above (will be freed by Db2) + ucp = (unsigned char*)(*groupList); + + cur = grpList2.first; + + // Format of the return is + while (cur != 0) + { + len = strlen(cur->name); + *ucp = (unsigned char)len; + ucp++; + + memcpy(ucp, cur->name, len); + ucp += len; + + (*groupCount)++; + + cur = cur->next; + } + +exit: + IAM_TRACE_EXIT("FindGroups", rc); + + if (grpList.first != NULL) freeList(&grpList); + if (grpList2.first != NULL) freeList(&grpList2); + if (*errorMessage != NULL) + { + *errorMessageLength = strlen(*errorMessage); + db2LogFunc(DB2SEC_LOG_ERROR, *errorMessage, *errorMessageLength); + } + return rc; +} + +/* FreeGroupList + * + * Free the group list information returned from FindGroups. + */ + +SQL_API_RC SQL_API_FN FreeGroupList(void *ptr, + char **errorMessage, + db2int32 *errorMessageLength) +{ + IAM_TRACE_ENTRY("FreeGroupList"); + if (ptr != NULL) free(ptr); + + *errorMessage = NULL; + *errorMessageLength = 0; + IAM_TRACE_EXIT("FreeGroupList", DB2SEC_PLUGIN_OK); + return(DB2SEC_PLUGIN_OK); +} + + + +/* DoesGroupExist + * Search for a user object where the AuthID attribute matches the + * value provided. + */ + +SQL_API_RC SQL_API_FN DoesGroupExist(const char *groupID, + db2int32 groupIDlength, + char **errorMessage, + db2int32 *errorMessageLength) +{ + int rc = DB2SEC_PLUGIN_OK; + IAM_TRACE_ENTRY("DoesGroupExist"); + + const char* userPoolID = read_userpool_from_cfg(); + rc = DoesAWSGroupExist(groupID, userPoolID); + if(rc != 0) + { + *errorMessage = "No user found in the given user pool"; + *errorMessageLength = strlen(*errorMessage); + goto exit; + } + else + { + *errorMessage = NULL; + *errorMessageLength = 0; + } +exit: + IAM_TRACE_EXIT("DoesGroupExist", rc); + return (rc); +} + + +/* db2secGroupPluginInit + * Plugin initialization. Parse the config file and set up function + * pointers. + */ + +SQL_API_RC SQL_API_FN db2secGroupPluginInit(db2int32 version, + void *group_fns, + db2secLogMessage *msgFunc, + char **errorMessage, + db2int32 *errorMessageLength) +{ + IAM_TRACE_ENTRY("db2secGroupPluginInit"); + int rc = DB2SEC_PLUGIN_OK; + db2secGroupFunction_1 *p; + + *errorMessage = NULL; + *errorMessageLength = 0; + p = (db2secGroupFunction_1*)group_fns; + + p->version = DB2SEC_GROUP_FUNCTIONS_VERSION_1; + p->plugintype = DB2SEC_PLUGIN_TYPE_GROUP; + p->db2secGetGroupsForUser = FindGroups; + p->db2secDoesGroupExist = DoesGroupExist; + p->db2secFreeGroupListMemory = FreeGroupList; + p->db2secFreeErrormsg = FreeErrorMessage; + p->db2secPluginTerm = PluginTerminate; + + db2LogFunc = msgFunc; + + IAM_TRACE_EXIT("db2secGroupPluginInit", rc); + if (*errorMessage != NULL) + { + *errorMessageLength = strlen(*errorMessage); + db2LogFunc(DB2SEC_LOG_ERROR, *errorMessage, *errorMessageLength); + } + return(rc); +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthserver.c b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthserver.c new file mode 100644 index 0000000..cda14b2 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSIAMauthserver.c @@ -0,0 +1,638 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** +** Source File Name = src/gss/AWSIAMauthserver.c +** +** Descriptive Name = Server side plugin that validates the AWS cognito user with accesstoken +** +** Function: Implements functions required by Db2 server plugin architeture +** This plugin is meant to be used with AWS IAM security plugin. +** +** With AWS IAM security plugin in place, when the AWS user uses an access token +** retrieved from AWS cognito for Db2 authentication, this plugin will validate all the +** parameters from the provided JWT token and allows user to connect to Db2 only if the token +** passes all the validity checks. +** +** +** Dependencies: User should be part of the Cognito userpool which is configured in a config file named +** ~/sqllib/security64/plugin/cfg/cognito_userpools.json. This is the default file location. +** One can override this location with the means of environment variable AWS_USERPOOL_CFG_ENV. +** +** Restrictions: The config file mentioned above supports only one userpool, which means user authenticating +** to Db2 can be part of only configured userpool. +** +** +*******************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "AWSIAMauth.h" +#include +#include "../common/base64.h" +#include "iam.h" +#include "jwt.h" +#include "AWSIAMauthfile.h" +#include "../common/AWSIAMtrace.h" +#include "AWSUserPoolInfo.h" +#include "utils.h" + CRED_T serverCred = {0, sizeof(PRINCIPAL_NAME)-1, PRINCIPAL_NAME, 0, ""}; + + +static db2secGetConDetails *getConDetails; + +OM_uint32 getClientIPAddress(char szIPAddress[], int maxLength) +{ + struct db2sec_con_details_3 conDetails; + SQL_API_RC ret = DB2SEC_PLUGIN_OK; + unsigned char * pIpAddress = NULL; + int length = 0; + IAM_TRACE_ENTRY("getClientIPAddress"); + + + // Obtain details about the client that is trying to attempt to have a database connection + ret = (getConDetails)(DB2SEC_CON_DETAILS_VERSION_3, &conDetails); + if ( ret != 0 ) + { + db2Log( DB2SEC_LOG_ERROR, "The database connection does not exist and needs to be started, ret = %d", ret ); + ret = GSS_S_UNAVAILABLE; + goto finish; + } + + // To fetch the client IP address which is in decimal format and convert it to a string + pIpAddress = (unsigned char *) &conDetails.clientIPAddress; + + // To print the IP address and store it in a variable + length = snprintf( szIPAddress, maxLength, "%u.%u.%u.%u", pIpAddress[0], pIpAddress[1], pIpAddress[2], pIpAddress[3] ); + if( length <= 0 || length >= maxLength ) + { + db2Log( DB2SEC_LOG_ERROR, "The client IP address (length = %d) is too long", length ); + ret = GSS_S_UNAVAILABLE; + goto finish; + } + +finish: + + IAM_TRACE_EXIT("getClientIPAddress",ret); + + return ret; +} + +/*-------------------------------------------------- + * SERVER SIDE FUNCTIONS + *--------------------------------------------------*/ + CONTEXT_T *create_server_context(const gss_buffer_t input_token, + CRED_T *pServerCred) +{ + AUTHINFO_T *pInToken = (AUTHINFO_T *)(input_token->value); + int useridLen = ByteReverse(pInToken->useridLen); + int authtokenLen = ByteReverse(pInToken->authTokenLen); + char *userid = &pInToken->data[authtokenLen]; + CONTEXT_T *pCtx = NULL; + IAM_TRACE_ENTRY("create_server_context"); + pCtx = (CONTEXT_T *)calloc(1, sizeof(CONTEXT_T)); + if (pCtx == NULL) return NULL; + + pCtx->targetLen = useridLen; + pCtx->target = (char *)malloc(pCtx->targetLen); + if (pCtx->target == NULL) goto malloc_fail; + memcpy(pCtx->target, userid, pCtx->targetLen); + + pCtx->sourceLen = pServerCred->useridLen; + pCtx->source = (char *)malloc(pCtx->sourceLen); + if (pCtx->source == NULL) goto malloc_fail; + memcpy(pCtx->source, pServerCred->userid, pCtx->sourceLen); + IAM_TRACE_EXIT("create_server_context", 0); + return pCtx; +malloc_fail: + if (pCtx->target) free(pCtx->target); + if (pCtx->source) free(pCtx->source); + if (pCtx) free(pCtx); + IAM_TRACE_EXIT("create_server_context", -1); + return NULL; +} + +int reset_userid(CONTEXT_T *pCtx, char *userid, int useridLen) +{ + int rc=0; + + char *newTarget = NULL; + IAM_TRACE_ENTRY("reset_userid"); + + // reset userid in context + newTarget = (char *)malloc(useridLen*sizeof(char)); + if (!newTarget) + { + rc = -1; + goto exit; + } + + memcpy(newTarget, userid, useridLen); + // hopefully we can assume no one else has access to pCtx but this thread + // free old target if set + if (pCtx->target) + { + free(pCtx->target); + } + pCtx->target = newTarget; + pCtx->targetLen = useridLen; + +exit: + IAM_TRACE_EXIT("reset_userid",rc); + return rc; +} + + +OM_uint32 validate_auth_info(CONTEXT_T *pCtx, const gss_buffer_t input_token, + gss_buffer_t output_token, gss_name_t *src_name, OM_uint32 *minor_status) +{ + + OM_uint32 rc = GSS_S_COMPLETE; + int ret = 0; + AUTHINFO_T *pInToken = (AUTHINFO_T *)(input_token->value); + AUTHINFO_T *pOutToken = NULL; + + int authmode = DB2SEC_AUTH_MODE_IAM; + int authtype = ByteReverse(pInToken->authType); + char *authtoken = &pInToken->data[0]; + int authtokenLen = ByteReverse(pInToken->authTokenLen); + + char* userInToken = NULL; + db2int32 authidLen = 0; + char useridtocheck[DB2SEC_MAX_USERID_LENGTH+1]=""; + db2int32 useridtocheckLen =0 ; + + unsigned char szIPAddress[IP_BUFFER_SIZE]=""; + IAM_TRACE_ENTRY("validate_auth_info"); + + ret = getClientIPAddress(szIPAddress, IP_BUFFER_SIZE); + + if(ret != 0) + { + db2Log( DB2SEC_LOG_ERROR, "Fail to retrieve the client IP address, ret = %d", ret ); + *minor_status = RETCODE_BADCONNECTION; + rc = GSS_S_UNAVAILABLE; + goto finish; + } + + db2Log( DB2SEC_LOG_INFO, "validate_auth_info: %s", authTypeToString[authtype]); + // Perform authentication + if (authtype == DB2SEC_AUTH_ACCESS_TOKEN) + { + // Check to see if the username is passed in with the token and if it is remove it from the token to validate it and + // use the userid for the lookup later. The userid will be specified in the form userid:apikey or userid:accesstoken + char * id_delim = NULL; + id_delim = (char*)memchr ((char*)authtoken, ':', authtokenLen); + + if (id_delim != NULL) + { + useridtocheckLen = id_delim - authtoken; + if( useridtocheckLen > DB2SEC_MAX_USERID_LENGTH ) + { + db2Log( DB2SEC_LOG_ERROR, "validate_auth_info: Userid from %s is longer than %d", szIPAddress, DB2SEC_MAX_USERID_LENGTH ); + *minor_status = RETCODE_BADTOKEN; + rc = GSS_S_DEFECTIVE_CREDENTIAL; + goto finish; + } + + // this is the userid to validate against + memcpy(useridtocheck, authtoken, useridtocheckLen); + + useridtocheck[useridtocheckLen] = '\0'; + stringToLower(useridtocheck); + + authtoken = id_delim+1; + + // subtract useridlen and the ":" from the length of the remaining token + authtokenLen = authtokenLen - (useridtocheckLen +1); + } + + ret = verify_access_token(authtoken, authtokenLen, &userInToken, &authidLen, authtype, szIPAddress, minor_status, logFunc); + if( authidLen == 0 ) + { + db2Log( DB2SEC_LOG_ERROR, "validate_auth_info: Fail to get the username from access token from %s", szIPAddress ); + rc = ret; + goto finish; + } + db2Log( DB2SEC_LOG_INFO, "Username %s is found in Token", userInToken); + } + else + { + db2Log( DB2SEC_LOG_ERROR, "validate_auth_info: Invalid auth type (%d) from %s", authtype, szIPAddress ); + *minor_status = RETCODE_BADPASS; + rc = GSS_S_DEFECTIVE_CREDENTIAL; + goto finish; + } + + if( rc == GSS_S_COMPLETE ) { + if(strlen(useridtocheck) > 0 && strcmp(useridtocheck, userInToken) != 0) + { + db2Log( DB2SEC_LOG_ERROR, "validate_auth_info: Provided username doesn't match with the username from access token" ); + goto finish; + } + goto success; + } + else + { + db2Log( DB2SEC_LOG_ERROR, "validate_auth_info: Fail to authorize user from %s with auth type (%s)", szIPAddress, authTypeToString[authtype] ); + goto finish; + } + +success: + db2Log( DB2SEC_LOG_INFO, "validate_auth_info: Successfully authenticated and authorized userid (%.*s) from %s with auth type (%s)", authidLen, userInToken, szIPAddress, authTypeToString[authtype] ); + + ret = reset_userid(pCtx, userInToken, authidLen); + if (ret != 0) + { + db2Log( DB2SEC_LOG_ERROR, "validate_auth_info: Fail to reset userid, ret = %d", ret ); + *minor_status = RETCODE_MALLOC; + rc = GSS_S_FAILURE; + goto finish; + } + /* Generate service token + * This is sent back to the client for mutual authentication. + * We send the hardcoded principle name and a zero length + * password. This sample plugin ignores this information on + * the client side. + */ + +populatetoken: + if (output_token->value == NULL) { + pOutToken = (AUTHINFO_T *)calloc(1, sizeof(AUTHINFO_T)); + if (pOutToken == NULL) + { + db2Log( DB2SEC_LOG_ERROR, "validate_auth_info: Fail to allocate memory" ); + *minor_status = RETCODE_MALLOC; + rc = GSS_S_FAILURE; + goto finish; + } + + output_token->value = (void *)pOutToken; + output_token->length = sizeof(AUTHINFO_T); + } + +finish: + IAM_TRACE_EXIT("validate_auth_info",rc); + if( userInToken != NULL ) free( userInToken ); + return rc; +} + +/* GetAuthIDs + * Return the DB2 Authorization IDs associated with the supplied + * context. + * + * At this point, the GSS-API context is assumed to have been + * established and the context handle is passed in as the token. + */ +SQL_API_RC SQL_API_FN GetAuthIDs(const char *userid, + db2int32 useridlen, + const char *usernamespace, /* ignored */ + db2int32 usernamespacelen, /* ignored */ + db2int32 usernamespacetype, /* ignored */ + const char *dbname, /* ignored */ + db2int32 dbnamelen, /* ignored */ + void **token, + char SystemAuthID[], + db2int32 *SystemAuthIDlen, + char InitialSessionAuthID[], + db2int32 *InitialSessionAuthIDlen, + char username[], + db2int32 *usernamelen, + db2int32 *initsessionidtype, + char **errormsg, + db2int32 *errormsglen) +{ + int rc = DB2SEC_PLUGIN_OK; + CONTEXT_T *pCtx = NULL; + IAM_TRACE_ENTRY("GetAuthIDs"); + + *errormsg = NULL; + *errormsglen = 0; + + pCtx = (CONTEXT_T *) (*token); + if( pCtx == NULL ) + { + IAM_TRACE_DATA( "GETAuthIDs","TRUSTED CONTEXT"); + if(useridlen < 1) + { + char dumpMsg[256] ="GetAuthIDs: userid and CTX is empty"; + rc = DB2SEC_PLUGIN_NO_CRED; + *errormsg = strdup(dumpMsg); + *errormsglen = strlen(dumpMsg); + goto exit; + } + + *usernamelen = useridlen; + memcpy(username, userid, *usernamelen); + } + else + { + *usernamelen = pCtx->targetLen; + memcpy(username, pCtx->target, *usernamelen); + } + + memcpy(SystemAuthID, username, *usernamelen); + *SystemAuthIDlen = *usernamelen; + memcpy(InitialSessionAuthID, username, *usernamelen); + *InitialSessionAuthIDlen = *usernamelen; + *initsessionidtype = 0; + +exit: + IAM_TRACE_EXIT("GetAuthIDs", rc); + + return(rc); +} + +/* DoesAuthIDExist() + * Does the supplied DB2 Authorization ID refer to a valid user? + */ +SQL_API_RC SQL_API_FN db2secDoesAuthIDExist(const char *authid, + db2int32 authidLen, + char **errorMsg, + db2int32 *errorMsgLen) + +{ + int rc = DB2SEC_PLUGIN_OK; + + IAM_TRACE_ENTRY("DoesAuthIDExist"); + + const char* userPoolID = read_userpool_from_cfg(); + rc = DoesAWSUserExist(authid, userPoolID); + if(rc != 0) + { + *errorMsg = "No user found in the given user pool"; + *errorMsgLen = strlen(*errorMsg); + goto exit; + } + else + { + *errorMsg = NULL; + *errorMsgLen = 0; + } +exit: + + IAM_TRACE_EXIT("DoesAuthIDExist",rc); + + return(rc); +} + + +/* gss_accept_sec_context() + * Process a token received from the client, including + * validating the encapsulated userid/password. + */ +OM_uint32 SQL_API_FN gss_accept_sec_context( + OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + const gss_cred_id_t acceptor_cred_handle, + const gss_buffer_t input_token, + const gss_channel_bindings_t input_chan_bindings, + gss_name_t *src_name, + gss_OID *mech_type, + gss_buffer_t output_token, + OM_uint32 *ret_flags, + OM_uint32 *time_rec, + gss_cred_id_t *delegated_cred_handle) +{ + OM_uint32 rc = GSS_S_COMPLETE; + CRED_T *pServerCred = NULL; + CONTEXT_T *pCtx = NULL; + int rc2; + int i; + + IAM_TRACE_ENTRY("gss_accept_sec_context"); + + db2Log( DB2SEC_LOG_INFO, "gss_accept_sec_context: Security plugin has been loaded" ); + /* Check for non-supported options and sanity checks */ + if (input_chan_bindings != GSS_C_NO_CHANNEL_BINDINGS) + { + rc = GSS_S_BAD_BINDINGS; + goto exit; + } + if (mech_type != NULL) + { + rc = GSS_S_BAD_MECH; + goto exit; + } + if (acceptor_cred_handle == GSS_C_NO_CREDENTIAL) + { + rc = GSS_S_DEFECTIVE_CREDENTIAL; + goto exit; + } + if (input_token == GSS_C_NO_BUFFER) + { + rc = GSS_S_DEFECTIVE_CREDENTIAL; + goto exit; + } + if (context_handle == GSS_C_NO_CONTEXT) + { + rc = GSS_S_NO_CONTEXT; + goto exit; + } + + /* remaining time is not required */ + if (time_rec != NULL) + { + *time_rec = 0; + } + + /* + * The acceptor_cred_handle should be our server credential. + */ + pServerCred = (CRED_T *)acceptor_cred_handle; + + /* On first call to init_sec_context, the context handle should + * be set to GSS_C_NO_CONTEXT; set up the context structure + */ + if (*context_handle != GSS_C_NO_CONTEXT) + { + pCtx = (CONTEXT_T *) *context_handle; + } + else + { + pCtx = create_server_context(input_token, pServerCred); + if (!pCtx) { + rc = GSS_S_NO_CONTEXT; + goto exit; + } + *(CONTEXT_T **)context_handle = pCtx; + } + + db2Log(DB2SEC_LOG_INFO, "validate_auth_info to be invoked"); + /* First invocation */ + if (pCtx->ctxCount == 0) + { + rc = validate_auth_info(pCtx, input_token, output_token, src_name, minor_status); + if (rc == GSS_S_FAILURE) { + delete_context(pCtx); + pCtx = NULL; + goto exit; + } + /* We're done. No more flows. Make the context count negative + * so that the next invocation will result in an error. + */ + pCtx->ctxCount = -2; + } + else + { + /* Function shouldn't have been called again for context establishment */ + rc = GSS_S_FAILURE; + *minor_status = 4; + } + + +exit: + + if (pCtx) + (pCtx->ctxCount)++; + + IAM_TRACE_EXIT("gss_accept_sec_context", rc); + + return(rc); +} + +/****************************************************************************** +* +* Function Name = gss_display_name +* +* Descriptive Name = +* +* Function = +* +* Dependencies = +* +* Restrictions = +* +* Input = +* +* Output = +* +* Normal Return = DB2SEC_PLUGIN_OK +* +* Error Return = +* +*******************************************************************************/ +OM_uint32 SQL_API_FN gss_display_name(OM_uint32 * minor_status, + const gss_name_t input_name, + gss_buffer_t output_name_buffer, + gss_OID * output_name_type) +{ + OM_uint32 rc = GSS_S_COMPLETE; + NAME_T *pName; + + IAM_TRACE_ENTRY("gss_display_name"); + + /* No name types supported */ + if (output_name_type != NULL) + { + rc = GSS_S_BAD_NAMETYPE; + goto exit; + } + + if (output_name_buffer) + { + pName = (NAME_T *) input_name; + output_name_buffer->length = pName->useridLen; + output_name_buffer->value = (void *) malloc(output_name_buffer->length); + strncpy((char *)(output_name_buffer->value), + pName->userid, + output_name_buffer->length); + db2Log( DB2SEC_LOG_INFO, "gss_display_name: %s", (char *)output_name_buffer->value); + } + else + { + rc = GSS_S_BAD_NAME; + goto exit; + } + +exit: + IAM_TRACE_EXIT("gss_display_name", rc); + + return(rc); +} + + +/* db2secServerAuthPluginInit() + * Set up plugin function pointers and perform other initialization. + * This function is called by name when the plugin is loaded. + */ +SQL_API_RC SQL_API_FN db2secServerAuthPluginInit(db2int32 version, + void *functions, + db2secGetConDetails *getConDetails_fn, + db2secLogMessage *msgFunc, + char **errormsg, + db2int32 *errormsglen) +{ + int rc = DB2SEC_PLUGIN_OK; + db2secGssapiServerAuthFunctions_1 *pFPs; + char *principalName; + int length; + + IAM_TRACE_ENTRY("db2secServerAuthPluginInit"); + /* No error message */ + *errormsg = NULL; + *errormsglen = 0; + + if (version < DB2SEC_API_VERSION) + { + rc = DB2SEC_PLUGIN_UNKNOWNERROR; + goto exit; + } + + pFPs = (db2secGssapiServerAuthFunctions_1 *)functions; + + pFPs->plugintype = DB2SEC_PLUGIN_TYPE_GSSAPI; + pFPs->version = DB2SEC_API_VERSION; + + /* Populate the server name */ + pFPs->serverPrincipalName.value = PRINCIPAL_NAME; + pFPs->serverPrincipalName.length = strlen(PRINCIPAL_NAME); + + /* Fill in the server's cred handle */ + pFPs->serverCredHandle = (gss_cred_id_t)&serverCred; + + /* Set up function pointers */ + pFPs->db2secGetAuthIDs = GetAuthIDs; + pFPs->db2secDoesAuthIDExist = db2secDoesAuthIDExist; + pFPs->gss_accept_sec_context = gss_accept_sec_context; + pFPs->gss_display_name = gss_display_name; + /* gssapi_common */ + pFPs->db2secFreeErrormsg = FreeErrorMessage; + pFPs->gss_delete_sec_context = gss_delete_sec_context; + pFPs->gss_display_status = gss_display_status; + pFPs->gss_release_buffer = gss_release_buffer; + pFPs->gss_release_cred = gss_release_cred; + pFPs->gss_release_name = gss_release_name; + pFPs->db2secServerAuthPluginTerm = PluginTerminate; + + logFunc = msgFunc; + getConDetails = getConDetails_fn; + if (initIAM(logFunc ) != OK) + { + rc = DB2SEC_PLUGIN_UNKNOWNERROR; + goto exit; + } + + db2Log( DB2SEC_LOG_INFO, "db2secServerAuthPluginInit AWS IAM: Security plugin has been loaded" ); + +exit: + IAM_TRACE_EXIT("PRINCIPAL_NAME", rc); + + return rc; +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSSDKRAII.cpp b/aws/security_plugins/db2-aws-iam/src/gss/AWSSDKRAII.cpp new file mode 100644 index 0000000..871bda1 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSSDKRAII.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/AWSSDKRAII.cpp (%W%) +** +** Descriptive Name = GSS based authentication plugin code that helps for AWS communication using AWS SDK APIs +** +** Function: This class will make sure the InitAPI and ShutdownAPI calls are not made multiple times. +** +** Dependencies: +** +** Restrictions: +** +***********************************************************************/ +#include "AWSSDKRAII.h" + + +std::atomic Initialize::mCount(0); + +Initialize::Initialize() +{ + const size_t origCount = mCount++; + + if (origCount == 0) + { + mOptions.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Info; + Aws::InitAPI(mOptions); + } +} + +Initialize::~Initialize() +{ + const size_t newCount = --mCount; + + if (newCount == 0) + { + Aws::ShutdownAPI(mOptions); + } +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSSDKRAII.h b/aws/security_plugins/db2-aws-iam/src/gss/AWSSDKRAII.h new file mode 100644 index 0000000..bc3eeb1 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSSDKRAII.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/AWSSDKRAII.h (%W%) +** +** Descriptive Name = GSS based authentication plugin code that helps for AWS communication using AWS SDK APIs +** +** Function: This class will make sure the InitAPI and ShutdownAPI calls are not made multiple times. +** +** Dependencies: +** +** Restrictions: +** +***********************************************************************/ + +#ifndef _AWS_SDK_RAII_H_ +#define _AWS_SDK_RAII_H_ +#include + + +class Initialize +{ +public: + Initialize(); + ~Initialize(); + Initialize(const Initialize& ) = delete; + Initialize& operator=(const Initialize& ) = delete; + +private: + Aws::SDKOptions mOptions; + static std::atomic mCount; +}; + +#endif // _AWS_SDK_RAII_H_ diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSUserGroupInfo.cpp b/aws/security_plugins/db2-aws-iam/src/gss/AWSUserGroupInfo.cpp new file mode 100644 index 0000000..825d939 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSUserGroupInfo.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/AWSUserGroupInfo.cpp (%W%) +** +** Descriptive Name = GSS based authentication plugin code that queries AWS for fetching user groups +** +** Function: The code in this file has functions that fetch group information for a particular user +** from AWS Cognito using AWS CPP SDK APIs. +** +** +***********************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include "AWSUserGroupInfo.h" +#include "../common/AWSIAMtrace.h" +#include "AWSSDKRAII.h" + +#include "AWSIAMauth.h" + +OM_uint32 FetchAWSUserGroups(const char *username, const char* userpoolID, AWS_USER_GROUPS_T** awsusergroups) +{ + IAM_TRACE_ENTRY("FetchAWSUserGroups"); + OM_uint32 ret = RETCODE_OK; + if(username == NULL || userpoolID == NULL) + { + return RETCODE_BADCFG; + } + db2Log( DB2SEC_LOG_INFO, "User groups to be fetched for username %s and poolID is %s", username, userpoolID); + // Initialize the AWS SDK + const Initialize init; + { + // Configure the Cognito Identity Provider client + Aws::Client::ClientConfiguration clientConfig; + Aws::CognitoIdentityProvider::CognitoIdentityProviderClient cognitoClient(clientConfig); + + // Specify the user pool ID and username + Aws::CognitoIdentityProvider::Model::AdminListGroupsForUserRequest request; + request.SetUsername(username); + request.SetUserPoolId(userpoolID); + + // Retrieve the user attributes + Aws::CognitoIdentityProvider::Model::AdminListGroupsForUserOutcome outcome = cognitoClient.AdminListGroupsForUser(request); + + if (outcome.IsSuccess()) + { + const auto& userGroups = outcome.GetResult().GetGroups(); + *awsusergroups = (AWS_USER_GROUPS_T*) malloc(sizeof(AWS_USER_GROUPS_T)); + (*awsusergroups)->groups = (groupInfo_t*) malloc(sizeof(groupInfo_t)* userGroups.size()); + (*awsusergroups)->groupCount = userGroups.size(); + + size_t i = 0; + for (const auto& group : userGroups) + { + (*awsusergroups)->groups[i].group_name = group.GetGroupName().c_str(); + (*awsusergroups)->groups[i].groupNameLen = group.GetGroupName().size(); + ++i; + } + } + else + { + db2Log( DB2SEC_LOG_ERROR, "Error retrieving user attributes: %s", outcome.GetError().GetMessage().c_str() ); + ret = RETCODE_AWS_NO_USER_ATTRI; + } + } + + return ret; +} + + +OM_uint32 DoesAWSGroupExist(const char* groupName, const char* userpoolID) +{ + IAM_TRACE_ENTRY("DoesGroupExist"); + OM_uint32 ret = RETCODE_OK; + if(groupName == NULL || userpoolID == NULL) + { + return RETCODE_BADCFG; + } + + db2Log( DB2SEC_LOG_INFO, "Group existence to be checked for group: %s", groupName); + // Initialize the AWS SDK + const Initialize init; + { + // Configure the Cognito Identity Provider client + Aws::Client::ClientConfiguration clientConfig; + Aws::CognitoIdentityProvider::CognitoIdentityProviderClient cognitoClient(clientConfig); + + // Specify the user pool ID and username + Aws::CognitoIdentityProvider::Model::GetGroupRequest request; + request.SetGroupName(groupName); + request.SetUserPoolId(userpoolID); + + // Retrieve the user attributes + Aws::CognitoIdentityProvider::Model::GetGroupOutcome outcome = cognitoClient.GetGroup(request); + + if (!outcome.IsSuccess()) + { + db2Log( DB2SEC_LOG_ERROR, "No such group in the user pool: %s ", userpoolID); + ret = RETCODE_AWS_NO_USER_ATTRI; + } + } + + return ret; +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSUserGroupInfo.h b/aws/security_plugins/db2-aws-iam/src/gss/AWSUserGroupInfo.h new file mode 100644 index 0000000..08d2621 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSUserGroupInfo.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/AWSUserGroupInfo.h (%W%) +** +** Descriptive Name = GSS based authentication plugin code that queries AWS for fetching user groups +** +** Function: The code in this file has functions that fetch group information for a particular user +** from AWS Cognito using AWS CPP SDK APIs. +** +** +***********************************************************************/ + +#ifndef _AWS_USER_GROUP_INFO_H_ +#define _AWS_USER_GROUP_INFO_H_ + +#include +#include +#include "iam.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _groupInfo { + const char *group_name; + size_t groupNameLen; +} groupInfo_t; + +typedef struct user_groups +{ + groupInfo_t* groups; + int groupCount; +} AWS_USER_GROUPS_T; + +OM_uint32 FetchAWSUserGroups(const char *username, const char* userpoolID, AWS_USER_GROUPS_T** awsusergroups); + +OM_uint32 DoesAWSGroupExist(const char* groupName, const char* userpoolID); + +#ifdef __cplusplus +} +#endif + +#define MAX_STASH_BUFFER_LEN 255 + +#endif //_AWS_USER_GROUP_INFO_H_ diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSUserPoolInfo.cpp b/aws/security_plugins/db2-aws-iam/src/gss/AWSUserPoolInfo.cpp new file mode 100644 index 0000000..c1d1caa --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSUserPoolInfo.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/AWSUserPoolInfo.cpp (%W%) +** +** Descriptive Name = GSS based authentication plugin code that queries AWS for fetching userpool +** and clients information +** +** Function: The code in this file has functions that fetches userpool clients in the given userpool +** from AWS Cognito using AWS CPP SDK APIs +** +** +***********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "AWSUserPoolInfo.h" +#include "AWSIAMauth.h" +#include "../common/AWSIAMtrace.h" +#include "AWSSDKRAII.h" + +char** ListClientsForUserPool(const char *userpool, OM_uint32* ret, size_t* count, db2secLogMessage *logFunc) +{ + char** outClientIDs = NULL; + // Initialize the AWS SDK + const Initialize init; + { + // Configure the Cognito Identity Provider client + Aws::Client::ClientConfiguration clientConfig; + Aws::CognitoIdentityProvider::CognitoIdentityProviderClient cognitoClient(clientConfig); + + Aws::CognitoIdentityProvider::Model::ListUserPoolClientsRequest request; + request.SetUserPoolId(userpool); + Aws::CognitoIdentityProvider::Model::ListUserPoolClientsOutcome outcome = cognitoClient.ListUserPoolClients(request); + db2Log(DB2SEC_LOG_INFO, "Client IDs requested from AWS from UserPool: %s ", userpool); + + if (outcome.IsSuccess()) { + + const auto& clientsDes = outcome.GetResult().GetUserPoolClients(); + *count = clientsDes.size(); + *ret = GSS_S_COMPLETE; + outClientIDs = (char **)malloc(*count * sizeof(char *)); + size_t i = 0; + for (const auto& client : clientsDes) { + const char* clientID = client.GetClientId().c_str(); + size_t len = strlen(clientID); + outClientIDs[i] = (char *)malloc((len + 1 ) * sizeof(char)); + if(outClientIDs[i] != NULL) + { + memset(outClientIDs[i], '\0', len+1); + memcpy(outClientIDs[i], clientID, len); + ++i; + } + } + } + else + { + *ret = RETCODE_AWS_NO_USER_ATTRI; + db2Log(DB2SEC_LOG_ERROR, "Error retrieving user attributes: %s ", outcome.GetError().GetMessage()); + } + } + + return outClientIDs; +} + +OM_uint32 DoesAWSUserExist(const char *username, const char* userpoolID) +{ + IAM_TRACE_ENTRY("DoesUserExist"); + OM_uint32 ret = RETCODE_OK; + if(username == NULL || userpoolID == NULL) + { + return RETCODE_BADCFG; + } + db2Log(DB2SEC_LOG_INFO, "User existence checked for username %s and poolID %s ", username, userpoolID); + // Initialize the AWS SDK + const Initialize init; + { + // Configure the Cognito Identity Provider client + Aws::Client::ClientConfiguration clientConfig; + Aws::CognitoIdentityProvider::CognitoIdentityProviderClient cognitoClient(clientConfig); + + Aws::CognitoIdentityProvider::Model::AdminGetUserRequest request; + request.SetUsername(username); + request.SetUserPoolId(userpoolID); + + Aws::CognitoIdentityProvider::Model::AdminGetUserOutcome outcome = + cognitoClient.AdminGetUser(request); + + if (!outcome.IsSuccess()) + { + db2Log(DB2SEC_LOG_ERROR, "No such user in the user pool: %s", userpoolID); + ret = RETCODE_AWS_NO_USER_ATTRI; + } + } + + return ret; +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/AWSUserPoolInfo.h b/aws/security_plugins/db2-aws-iam/src/gss/AWSUserPoolInfo.h new file mode 100644 index 0000000..4362b82 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/AWSUserPoolInfo.h @@ -0,0 +1,43 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/AWSUserPoolInfo.h (%W%) +** +** Descriptive Name = GSS based authentication plugin code that queries AWS for fetching userpool +** and clients information +** +** Function: The code in this file has functions that fetches userpool clients in the given userpool +** from AWS Cognito using AWS CPP SDK APIs +** +** +***********************************************************************/ + +#ifndef _AWS_USERPOOL_INFO_H_ +#define _AWS_USERPOOL_INFO_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +char** ListClientsForUserPool(const char *userpool, OM_uint32* ret, size_t* count, db2secLogMessage *logFunc); +OM_uint32 DoesAWSUserExist(const char *username, const char* userpoolID); + +#ifdef __cplusplus +} +#endif + +#endif // _AWS_USERPOOL_INFO_H_ diff --git a/aws/security_plugins/db2-aws-iam/src/gss/curl_response.h b/aws/security_plugins/db2-aws-iam/src/gss/curl_response.h new file mode 100644 index 0000000..d5fe6d2 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/curl_response.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/curl_response.h (%W%) +** +** Descriptive Name = Header file for curl related operations +** +** Function: +** +* +* +*********************************************************************************/ + +#ifndef _CURL_RESPONSE_H_ +#define _CURL_RESPONSE_H_ +#include +#include + +struct write_buf { + char *ptr; /* null terminated */ + size_t size; /* size excluding null */ +}; + +static size_t write_callback(char *ptr, size_t size, + size_t nmemb, void *userdata) +{ + size_t bytes = size * nmemb; + struct write_buf *buf = (struct write_buf *) userdata; + + buf->ptr = (char *) realloc(buf->ptr, buf->size + bytes + 1); + if (!buf->ptr) { + return 0; + } + + memcpy(buf->ptr + buf->size, ptr, bytes); + buf->size += bytes; + buf->ptr[buf->size] = 0; + + return bytes; +} + +static size_t no_output_callback(void *ptr, size_t size, + size_t nmemb, void *userdata) +{ + return size * nmemb; +} + +#endif diff --git a/aws/security_plugins/db2-aws-iam/src/gss/iam.cpp b/aws/security_plugins/db2-aws-iam/src/gss/iam.cpp new file mode 100755 index 0000000..4fa2f00 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/iam.cpp @@ -0,0 +1,411 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/iam.cpp (%W%) +** +** Descriptive Name = Server side GSS based authentication plugin code +** +** Function: +** +** +***********************************************************************/ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include "AWSIAMauth.h" +#include "AWSIAMauthfile.h" +#include "iam.h" +#include "jwt.h" +#include "curl_response.h" +#include "../common/AWSIAMtrace.h" +#include "AWSUserPoolInfo.h" +#include "iam.h" +#include "utils.h" +#define MAX_STR_LEN 8192 + +#define MAX_STASH_FILE_PATH_LEN 255 +#define MAX_AUTH_INFO_TYPE 6 + +int authType; + + + +/******************************************************************* +* +* Function Name = initIAM +* +* Descriptive Name = Initialize IAM environment +* +* Dependencies = None +* +* Restrictions = None +* +* Input = logFunc - A pointer to the db2secLogMessage API +* for saving debugging messages in db2diag.log +* +* Output = None +* +* Normal Return = OK +* +* Error Return = INIT_FAIL +* +******************************************************************/ +int initIAM(db2secLogMessage *logFunc) +{ + IAM_TRACE_ENTRY("initIAM"); + IAM_TRACE_EXIT("initIAM", 0); + return 0; +} + +/******************************************************************* +* +* Function Name = verify_access_token +* +* Descriptive Name = Validates various parameters of JWT token +* +* Dependencies = +* +* Restrictions = +* +* Input = authtoken, authtokenLen - IAM access token +* logFunc - A pointer to the db2secLogMessage API +* for saving debugging messages in db2diag.log +* +* Output = authid, authIDLen - username and its length from JWT token +* +* Normal Return = GSS_S_COMPLETE +* +* Error Return = GSS_S_DEFECTIVE_TOKEN +* +******************************************************************/ +OM_uint32 verify_access_token(const char *authtoken, size_t authtokenLen, char** authID, db2int32* authIDLen, + int authType, char* szIPAddress, OM_uint32 *minorStatus, db2secLogMessage *logFunc) +{ + OM_uint32 ret = GSS_S_COMPLETE; + + char* userPoolIDOfUser = NULL; + char* iss = NULL; + char* tokenInUse = NULL; + char* exp = NULL; + json_object* jwt_obj = NULL; + + size_t issLen = 0; + size_t tokenInUseLen = 0; + + char* clientFieldInJWT = NULL; + char* usernameFieldInJWT = NULL; + + ret = get_json_object_from_jwt(authtoken, authtokenLen, &jwt_obj, logFunc); + + if(ret != GSS_S_COMPLETE || jwt_obj == NULL) + { + *minorStatus = RETCODE_BADTOKEN; + goto finish; + } + db2Log(DB2SEC_LOG_INFO, "Got json object from JWT "); + + tokenInUseLen = get_string_element_from_jwt(jwt_obj, "token_use", &tokenInUse, 32); // TODO: Remove hard-coding + db2Log(DB2SEC_LOG_INFO, "Got token type as %s ", tokenInUse); + if(tokenInUseLen == 0) + { + db2Log( DB2SEC_LOG_WARNING, "verify_access_token: Fail to get token_use parameter from %s with auth type (%s)", szIPAddress, authTypeToString[authType] ); + *minorStatus = RETCODE_BADTOKEN; + IAM_TRACE_DATA("verify_access_token", "BAD_TOKEN"); + ret = GSS_S_DEFECTIVE_CREDENTIAL; + goto finish; + } + if(strcmp(tokenInUse, "id") == 0){ + clientFieldInJWT = (char*) malloc(sizeof(char) * 4 ); + strcpy(clientFieldInJWT, "aud"); + clientFieldInJWT[3] = '\0'; + + usernameFieldInJWT = (char*) malloc(sizeof(char) * 18 ); + strcpy(usernameFieldInJWT, "cognito:username"); + usernameFieldInJWT[17] = '\0'; + } + else if (strcmp(tokenInUse, "access") == 0) + { + clientFieldInJWT = (char*) malloc(sizeof(char) * 11 ); + strcpy(clientFieldInJWT, "client_id"); + clientFieldInJWT[10] = '\0'; + + usernameFieldInJWT = (char*) malloc(sizeof(char) * 10 ); + strcpy(usernameFieldInJWT, "username"); + usernameFieldInJWT[9] = '\0'; + } + + issLen = get_string_element_from_jwt(jwt_obj, "iss", &iss, DB2SEC_MAX_USERID_LENGTH); + if(issLen == 0) + { + db2Log( DB2SEC_LOG_ERROR, "verify_access_token: Fail to get Issuer in access token from %s with auth type (%s)", szIPAddress, authTypeToString[authType] ); + *minorStatus = RETCODE_BADTOKEN; + IAM_TRACE_DATA("verify_access_token", "BAD_TOKEN"); + ret = GSS_S_DEFECTIVE_CREDENTIAL; + goto finish; + } + db2Log(DB2SEC_LOG_INFO, "Got issuer from JWT "); + ret = validate_user_poolID(iss, &userPoolIDOfUser); + if(ret != GSS_S_COMPLETE) + { + db2Log( DB2SEC_LOG_ERROR, "verify_access_token: Found invalid user pool ID of access token from %s with auth type (%s)", szIPAddress, authTypeToString[authType] ); + *minorStatus = RETCODE_BADTOKEN; + IAM_TRACE_DATA("verify_access_token", "BAD_TOKEN"); + ret = GSS_S_DEFECTIVE_CREDENTIAL; + goto finish; + } + db2Log( DB2SEC_LOG_INFO, "Got userpoolID is verified"); + ret = verify_jwt_header_and_signature(authtoken, authtokenLen, iss, logFunc); + if(ret != GSS_S_COMPLETE) + { + db2Log( DB2SEC_LOG_ERROR, "verify_access_token: Invalid header signature of access token from %s with auth type (%s)", szIPAddress, authTypeToString[authType] ); + *minorStatus = RETCODE_BADTOKEN; + IAM_TRACE_DATA("verify_access_token", "BAD_TOKEN"); + ret = GSS_S_DEFECTIVE_CREDENTIAL; + goto finish; + } + db2Log( DB2SEC_LOG_INFO, "Signature of JWT is verified"); + ret = validate_access_token_expiry(jwt_obj, minorStatus, logFunc); + if( ret != GSS_S_COMPLETE ) + { + IAM_TRACE_DATA("verify_access_token", "EXPIRED_TOKEN"); + *minorStatus = GSS_S_CREDENTIALS_EXPIRED; + db2Log( DB2SEC_LOG_ERROR, "verify_access_token: Fail to authenticate an expired access token from %s with auth type (%s), ret = %d", szIPAddress, authTypeToString[authType], ret ); + // No need to set minorStatus and rc as validate_access_token_expiry takes care of them + goto finish; + } + db2Log( DB2SEC_LOG_INFO, "Expiry time of JWT is verified"); + + ret = validate_access_token_clientID(jwt_obj, clientFieldInJWT, userPoolIDOfUser, minorStatus, logFunc); + if( ret != GSS_S_COMPLETE ) + { + IAM_TRACE_DATA("verify_access_token", "INVALID_AUD or INVALID_CLIENTID"); + db2Log( DB2SEC_LOG_ERROR, "verify_access_token: Found invalid audience or client ID from access token from %s", szIPAddress ); + *minorStatus = RETCODE_BADTOKEN; + ret = GSS_S_DEFECTIVE_CREDENTIAL; + goto finish; + } + db2Log(DB2SEC_LOG_INFO, "ClientID is verified"); + + *authIDLen = get_string_element_from_jwt(jwt_obj, usernameFieldInJWT, authID, DB2SEC_MAX_USERID_LENGTH); + db2Log(DB2SEC_LOG_INFO, "AUTHID len is as %d ", *authIDLen); + if( authIDLen == 0 ) + { + IAM_TRACE_DATA("verify_access_token", "INVALID_AUD or INVALID_CLIENTID"); + db2Log( DB2SEC_LOG_ERROR, "verify_access_token: No username is found from access token from %s", szIPAddress ); + *minorStatus = RETCODE_BADTOKEN; + ret = GSS_S_DEFECTIVE_CREDENTIAL; + } + db2Log(DB2SEC_LOG_INFO, "Got username as %s ", *authID); + ret = GSS_S_COMPLETE; + goto finish; + +finish: + if ( jwt_obj ) + { + json_object_put(jwt_obj); + } + return ret; +} + + +/******************************************************************* +* +* Function Name = validate_access_token_expiry +* +* Descriptive Name = Extract access token expiry from given access token +* and compare with current system time +* +* Dependencies = +* +* Restrictions = +* +* Input = apikey/apikeyLen - IAM access token +* logFunc - A pointer to the db2secLogMessage API +* for saving debugging messages in db2diag.log +* +* Output = minorStatus - minor status code +* +* Normal Return = GSS_S_COMPLETE +* +* Error Return = GSS_S_DEFECTIVE_TOKEN +* +******************************************************************/ +OM_uint32 validate_access_token_expiry(const json_object* jwt_obj, + OM_uint32 *minorStatus, + db2secLogMessage *logFunc) +{ + db2Uint64 expiry = 0; + time_t currentTime; + char keyName[4] = "exp"; + OM_uint32 rc = GSS_S_COMPLETE; + IAM_TRACE_ENTRY("validate_access_token_expiry"); + + *minorStatus = RETCODE_OK; + + // Get access token expiry + expiry = get_int64_element_from_jwt(jwt_obj, keyName); + if (expiry == 0) + { + db2Log(DB2SEC_LOG_ERROR, "validate_access_token_expiry: Fail to get the token expiry"); + *minorStatus = RETCODE_BADTOKEN; + rc = GSS_S_DEFECTIVE_TOKEN; + return rc; + } + + // Get current system time in seconds + currentTime = time(NULL); + + // Compare access token expiry and current time + if (currentTime >= expiry) + { + db2Log(DB2SEC_LOG_ERROR, "validate_access_token_expiry: access token has expired"); + *minorStatus = RETCODE_BADTOKEN; // TODO: Check seg fault here due to db2Log + rc = GSS_S_CREDENTIALS_EXPIRED; + } + + return rc; +} + +/******************************************************************* +* +* Function Name = validate_access_token_clientID +* +* Descriptive Name = Extract aud field from given access token +* and compare it with expected client IDs +* +* Dependencies = +* +* Restrictions = +* +* Input = authToken - IAM access token +* logFunc - A pointer to the db2secLogMessage API +* for saving debugging messages in db2diag.log +* +* Output = minorStatus - minor status code +* +* Normal Return = GSS_S_COMPLETE +* +* Error Return = GSS_S_DEFECTIVE_TOKEN +* +******************************************************************/ +OM_uint32 validate_access_token_clientID(const json_object* jwt_obj, const char* clientFieldInJWT, + char* userPoolID, + OM_uint32 *minor_status, + db2secLogMessage *logFunc) +{ + OM_uint32 rc = GSS_S_COMPLETE; + char* audClientID = NULL; + int audInTokenLen = 0; + bool matched = false; + char** clientIDs = NULL; + size_t countOfClientIDs = 0; + IAM_TRACE_ENTRY("validate_access_token_clientID"); + *minor_status = 0; + + audInTokenLen = get_string_element_from_jwt(jwt_obj, clientFieldInJWT, &audClientID, DB2SEC_MAX_USERID_LENGTH); + + if (audInTokenLen == 0) + { + db2Log(DB2SEC_LOG_ERROR, "validate_access_token_clientID: Fail to get the audience from access token\n"); + *minor_status = RETCODE_BADTOKEN; + rc = GSS_S_DEFECTIVE_CREDENTIAL; + IAM_TRACE_DATA("validate_access_token_clientID", "NO_SUB_IN_TOKEN"); + goto exit; + } + + clientIDs = ListClientsForUserPool(userPoolID, &rc, &countOfClientIDs, logFunc); + + if (rc != GSS_S_COMPLETE) + { + db2Log(DB2SEC_LOG_ERROR, "validate_access_token_clientID: Failed to get ClientIDs from AWS\n"); + *minor_status = RETCODE_AWS_NO_USER_ATTRI; + rc = GSS_S_FAILURE; + IAM_TRACE_DATA("validate_access_token_clientID", "Failed to get ClientIDs from AWS"); + goto exit; + } + + for ( size_t i = 0; i < countOfClientIDs; ++i ) + { + if (clientIDs[i] && strncmp(clientIDs[i], audClientID, MAX_STR_LEN) == 0) + { + rc = RETCODE_OK; + matched = true; + IAM_TRACE_DATA("validate_access_token_clientID: Matched ClientID with aud", clientIDs[i]); + break; + } + } + if (! matched ) + { + rc = GSS_S_DEFECTIVE_TOKEN; + db2Log(DB2SEC_LOG_ERROR, "validate_access_token_clientID: Fail to get matching audience from access token\n"); + *minor_status = RETCODE_BADTOKEN; + } + + goto exit; + +exit: + IAM_TRACE_EXIT("validate_access_token_clientID",rc); + if (clientIDs != NULL) + { + for(size_t j = 0; j < countOfClientIDs; ++j) + { + free(clientIDs[j]); + } + } + free(clientIDs); + return rc; +} + +size_t validate_user_poolID(const char* iss, char** poolID) +{ + size_t rc = RETCODE_OK; + char* lastSlash = strrchr(const_cast(iss), '/'); + size_t NBytes = 0; + + bool matched = false; + if(lastSlash == NULL) + { + rc = RETCODE_BADTOKEN; + return rc; + } + + NBytes = (iss + strlen(iss) - lastSlash + 1)/sizeof(char); + *poolID = (char*)malloc(NBytes); + + //UserpoolID starts from last slash in the issuer's URL + memcpy(*poolID, (lastSlash + (sizeof(char)*1)), NBytes); + + const char* configuredPoolID = read_userpool_from_cfg(); + + if (configuredPoolID && strncmp(configuredPoolID, *poolID, MAX_STR_LEN) == 0) + { + rc = RETCODE_OK; + IAM_TRACE_DATA("read_userpool_from_cfg: Matched userpool ID", *poolID); + } + else + { + rc = RETCODE_BADTOKEN; + } + return rc; +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/iam.h b/aws/security_plugins/db2-aws-iam/src/gss/iam.h new file mode 100644 index 0000000..9fd2824 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/iam.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/iam.h (%W%) +** +** Descriptive Name = Header file for the Server side GSS based authentication +** plugin code (iam.cpp) +** +** Function: This file contains functions that validates JWT token and its claims +** +** +** +***********************************************************************/ + +#ifndef _IAM_H_ +#define _IAM_H_ + +#include +#include +#include + +#define OK 0 +#define INIT_FAIL 1 +#define SET_HEADER_FAIL 2 +#define ENCODE_FAIL 3 +#define CONSTRUCT_BODY_FAIL 4 +#define CURL_FAIL 5 +#define VERIFICATION_FAIL 6 +#define GET_PASSWORD_FROM_STASH_FAIL 7 +#define DB2_HOME_NOT_DEFINED 8 +#define CONSTRUCT_BEARER_FAIL 10 +#define DB2SEC_AUTH_MODE_IAM 11 // Authenticate using IAM + +#define URL_MAX_LEN 255 +#define REST_API_PORT 9999 + +//#define REST_API_NAME "ext-auth-rest" +#define REST_API_NAME "localhost" + + +extern int authType; +#ifdef __cplusplus +extern "C" { +#endif + +int initIAM(db2secLogMessage *logFunc); +OM_uint32 verify_access_token(const char *authtoken, size_t authtokenLen, char** authID, db2int32* authIDLen, + int authType, char* szIPAddress, OM_uint32 *minorStatus, db2secLogMessage *logFunc); +OM_uint32 validate_access_token_expiry(const json_object *jwt_obj, OM_uint32 *minorStatus, db2secLogMessage *logFunc); +OM_uint32 validate_access_token_clientID(const json_object *jwt_obj, const char* clientFieldInJWT, char* userPoolID, OM_uint32 *minorStatus, db2secLogMessage *logFunc); +size_t validate_user_poolID(const char* iss, char** poolID); + +#ifdef __cplusplus +} +#endif + +#define MAX_STASH_BUFFER_LEN 255 + +#endif diff --git a/aws/security_plugins/db2-aws-iam/src/gss/jwk.c b/aws/security_plugins/db2-aws-iam/src/gss/jwk.c new file mode 100644 index 0000000..0a73232 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/jwk.c @@ -0,0 +1,385 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/jwk.c (%W%) +** +** Descriptive Name = JSON Web Key operation code +** +** Function: Queries JSON Web Key Set and retrieves JSON Web key +** +** +*****************************************************************************/ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include "AWSIAMauth.h" +#include "AWSIAMauthfile.h" +#include "../common/base64.h" +#include "curl_response.h" +#include "jwk.h" + +#define MAX_STR_LEN 8192 + +/************************************************************************************** +* +* Function Name = jwt_kid +* +* Descriptive Name = Retrieve Key ID(kid) string from JWT +* +* Dependencies = +* +* Restrictions = +* +* Input = jwt/jwt_len - JSON Web Token +* kid - Key ID, returned string needs to be freed for this variable +* logFunc - A pointer to the db2secLogMessage API +* for saving debugging messages in db2diag.log +* +* Output = +* +* Normal Return = JWK_OK +* +* Error Return = JWK_BADHEADER, JWK_JSONTOKEN_ERR, JWK_OBJ_ERR, etc. +* +****************************************************************************************/ +static int jwt_kid(const char *jwt, size_t jwt_len, char** kid, db2secLogMessage *logFunc) +{ + char *end; + char *json_header = NULL; + size_t json_len = 0; + json_tokener *tok = NULL; + json_object *jwt_obj = NULL; + json_object *kid_obj = NULL; + int rc = JWK_OK; + json_bool found = FALSE; + const char *kid_str = NULL; + assert(jwt); + + // Decode header + end = memchr(jwt, '.', jwt_len); + if ( !end ) + { + db2Log( DB2SEC_LOG_ERROR, "jwt_kid: error in jwk header format\n" ); + rc = JWK_BADHEADER; + goto exit; + } + + json_len = base64_decode(jwt, end - jwt, (unsigned char **)&json_header); + if ( !json_header ) + { + db2Log( DB2SEC_LOG_ERROR, "jwt_kid: error in jwk header format\n" ); + rc = JWK_BADHEADER; + goto exit; + } + + tok = json_tokener_new(); + if ( !tok ) + { + free(json_header); + db2Log( DB2SEC_LOG_ERROR, "jwt_kid: failed to create JSON token\n" ); + rc = JWK_JSONTOKEN_ERR; + goto exit; + } + + jwt_obj = json_tokener_parse_ex(tok, json_header, json_len); + free(json_header); + + if ( !jwt_obj ) + { + json_tokener_free(tok); + db2Log( DB2SEC_LOG_ERROR, "jwt_kid: failed to parse JSON token\n" ); + rc = JWK_JSONTOKEN_ERR; + goto exit; + } + // Retrieve the key id from the jwt object. + found = json_object_object_get_ex(jwt_obj, "kid", &kid_obj); + json_tokener_free(tok); + if ( !found ) + { + db2Log( DB2SEC_LOG_ERROR, "jwt_kid: failed to retrieve kid object from jwt\n" ); + rc = JWK_OBJ_ERR; + goto exit; + } + // Extract the key string. + kid_str = json_object_get_string(kid_obj); + if( kid_str ) + { + *kid = strndup(kid_str, MAX_STR_LEN); + } + +exit: + if ( rc != JWK_OK ) + { + *kid = NULL; + } + if ( jwt_obj ) + { + json_object_put(jwt_obj); + } + return rc; +} + +/***************************************************************************** +* +* Function Name = query_jwks +* +* Descriptive Name = Retrieve JSON Web Key Set +* +* Dependencies = +* +* Restrictions = +* +* Input = jwk_url - JWKS(JSON Web Key Set) location +* jwks_ptr - pointer to jwks (this variable must be +* freed by a caller) +* logFunc - A pointer to the db2secLogMessage API +* for saving debugging messages in db2diag.log +* +* Output = +* +* Normal Return = JWK_OK +* +* Error Return = JWK_CURLE_FAIL, JWK_CURLE_RES_FAIL, etc. +* +******************************************************************************/ +int query_jwks(const char *jwk_url, char** jwks_json_ptr, db2secLogMessage *logFunc) +{ + int rc = JWK_OK; + struct write_buf response_body = { 0 }; + CURL *curl = curl_easy_init(); + long http_code = 0; + CURLcode res = CURLE_OK; + + response_body.ptr = NULL; + if ( !curl ) + { + db2Log( DB2SEC_LOG_ERROR, "query_jwks: Failed to initialize CURLE\n" ); + rc = JWK_CURLE_INIT_ERR; + goto exit; + } + // Perform CURL operation to get JSON Web Keys. + curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + //curl_easy_setopt(curl, CURLOPT_CAPATH, "/etc/ssl/certs/"); + curl_easy_setopt(curl, CURLOPT_URL, jwk_url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response_body); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + rc = JWK_CURLE_FAIL; + db2Log( DB2SEC_LOG_ERROR, "query_jwks: Failed to perform CURLE operation\n" ); + goto exit; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code != 200 || !response_body.ptr) + { + db2Log( DB2SEC_LOG_ERROR, "query_jwks: Failed to receive CURLE operation response\n" ); + rc = JWK_CURLE_RES_FAIL; + goto exit; + } + *jwks_json_ptr = response_body.ptr; +exit: + if ( rc != 0 ) + { + if (response_body.ptr) + { + free(response_body.ptr); + } + *jwks_json_ptr = NULL; + } + curl_easy_cleanup(curl); + return rc; +} + +/***************************************************************************** +* +* Function Name = find_jwk_by_kid +* +* Descriptive Name = Retrieve JWK from JWKS using kid(Key ID) +* +* Dependencies = +* +* Restrictions = +* +* Input = jwks_json - JSON Web Key Set +* kid - kid (Key ID) string + jwk_ptr - pointer to JWK Object +* logFunc - A pointer to the db2secLogMessage API +* for saving debugging messages in db2diag.log +* +* Output = +* +* Normal Return = JWK_OK +* +* Error Return = JWK_JSONTOKEN_ERR, JWK_OBJ_ERR, etc. +* +******************************************************************************/ +static int find_jwk_by_kid(const char *jwks_json, const char *kid, json_object** jwk_ptr, db2secLogMessage *logFunc) +{ + int rc = JWK_OK; + json_object *keys_arr = NULL; + json_object *jwks_obj = NULL; + json_bool found = FALSE; + int i = 0; + int keys_nr = 0; + assert(jwks_json); + assert(kid); + + jwks_obj = json_tokener_parse(jwks_json); + if ( !jwks_obj ) + { + rc = JWK_JSONTOKEN_ERR; + db2Log( DB2SEC_LOG_ERROR, "find_jwk_by_kid: Failed to parse jwks JSON token\n" ); + goto exit; + } + + found = json_object_object_get_ex(jwks_obj, "keys", &keys_arr); + if ( !found ) + { + rc = JWK_OBJ_ERR; + db2Log( DB2SEC_LOG_ERROR, "find_jwk_by_kid: Failed to retrieve keys array object\n" ); + goto exit; + } + + keys_nr = json_object_array_length(keys_arr); + // Loop through array of keys and get the required key corresponding to the kid. + for (i = 0; i < keys_nr; ++i) + { + json_object *kid_obj = NULL; + const char *kid_str = NULL; + json_object *key_obj = json_object_array_get_idx(keys_arr, i); + if ( !key_obj ) + { + continue; + } + found = json_object_object_get_ex(key_obj, "kid", &kid_obj); + if ( !found ) + { + continue; + } + kid_str = json_object_get_string(kid_obj); + if (kid_str && strncmp(kid_str, kid, MAX_STR_LEN) == 0) + { + json_object_get(key_obj); + *jwk_ptr = key_obj; + break; + } + } + // Check for key being not found in jwks. + if ( i == keys_nr ) + { + rc = JWK_NOT_FND; + db2Log( DB2SEC_LOG_ERROR, "find_jwk_by_kid: Kid %s not found.\n", kid ); + } + +exit: + if ( rc != 0 ) + { + *jwk_ptr = NULL; + } + if ( jwks_obj ) + { + json_object_put(jwks_obj); + } + return rc; +} + +/**************************************************************************** +* +* Function Name = get_jwk +* +* Descriptive Name = Retrieve JWK from JWT +* +* Dependencies = +* +* Restrictions = +* +* Input = jwt/jwt_len - JSON Web Token +* jwk_ptr - A pointer to JWK object +* logFunc - A pointer to the db2secLogMessage API +* for saving debugging messages in db2diag.log +* +* Output = +* +* Normal Return = JWK_OK +* +* Error Return = JWK_BADHEADER, JWK_JSONTOKEN_ERR, JWK_OBJ_ERR, etc. +* +*****************************************************************************/ +int get_jwk(const char *jwt, size_t jwt_len, const char* iss, json_object** jwk_ptr, db2secLogMessage *logFunc) +{ + int rc = JWK_OK; + char *kid = NULL; + static char *jwks_json = NULL; + json_object *jwk = NULL; + char *db2_home = getenv( "DB2_HOME" ); + + assert(jwt); + // Get the key id (kid) string for the corresponding jwk. + rc = jwt_kid(jwt, jwt_len, &kid, logFunc); + if ( rc != 0 ) + { + goto cleanup; + } + if(jwks_json == NULL) + { + // Get the jwks. + if( db2_home != NULL && iss != NULL ) + { + char keysURL[256]; + sprintf( keysURL, "%s/.well-known/jwks.json", iss); + rc = query_jwks(keysURL, &jwks_json, logFunc); + if ( rc != 0 ) + { + if(jwks_json != NULL) + { + free(jwks_json); + jwks_json = NULL; + } + goto cleanup; + } + } + } + // Find the required jwk from jwks using the key id string. + rc = find_jwk_by_kid(jwks_json, kid, jwk_ptr, logFunc); + if ( rc != 0 ) + { + free(jwks_json); + jwks_json = NULL; + + goto cleanup; + } + +cleanup: + if (kid) + { + free(kid); + } + + if ( rc != 0 ) + { + jwk = NULL; + db2Log( DB2SEC_LOG_ERROR, "get_jwk: get_jwk failed with error code %d\n", rc ); + } + return rc; +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/jwk.h b/aws/security_plugins/db2-aws-iam/src/gss/jwk.h new file mode 100644 index 0000000..3a436c5 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/jwk.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/jwk.h (%W%) +** +** Descriptive Name = Header file for JSON Web Key operation code (jwk.c) +** +** Function: Queries JSON Web Key Set and retrieves JSON Web key +** +** +***************************************************************************/ + +#ifndef _JWK_H_ +#define _JWK_H_ +#include +#include + +#define JWK_OK 0 /* Operation completed successfully */ +#define JWK_BADHEADER 1 /* JWK has bad header format */ +#define JWK_JSONTOKEN_ERR 2 /* ERROR in JSON TOKEN */ +#define JWK_CURLE_INIT_ERR 3 /* Failure in Initializing CURL */ +#define JWK_CURLE_FAIL 4 /* CURL Operation failure */ +#define JWK_CURLE_RES_FAIL 5 /* Failure in reception of CURL operation */ +#define JWK_OBJ_ERR 6 /* ERROR in JWK Object */ +#define JWK_NOT_FND 7 /* JWK not found in JWKS */ +#define JWK_MOD_ERR 8 /* Modulus component not found in jwk */ +#define JWK_EXP_ERR 9 /* Encryption component not found in jwk */ +#define JWT_BIGNUM_ERR 10 /* BIGNUM conversion failure */ +#define JWT_ERR 11 /* ERROR in JWT */ +#define JWT_RSA_ERR 12 /* RSA Object ERROR */ +#define JWT_RSA_KEY_ERR 13 /* RSA Key ERROR */ +#define JWT_RSA_PRV_KEY_ERR 14 /* RSA private key ERROR */ +#define JWT_PRV_KEY_CTX_ERR 15 /* RSA private key digest context ERROR */ +#define JWT_NO_BN_NEW_FAILED 16 /* Error getting new BIGNUMBER */ +int get_jwk(const char *jwt, size_t jwt_len, const char* iss, json_object** jwk_ptr, db2secLogMessage *logFunc); +int query_jwks(const char *jwk_url, char** jwks_json_ptr, db2secLogMessage *logFunc); +#endif + diff --git a/aws/security_plugins/db2-aws-iam/src/gss/jwt.c b/aws/security_plugins/db2-aws-iam/src/gss/jwt.c new file mode 100644 index 0000000..3e50723 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/jwt.c @@ -0,0 +1,578 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/jwt.c (%W%) +** +** Descriptive Name = JSON Web Token operation code +** +** Function: This code contains functions that retrieves various types of data from given json object. + It also has functions that validates header and signature of the JWT. +** +** +*****************************************************************************/ + + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "AWSIAMauthfile.h" +#include "../common/base64.h" +#include "jwk.h" +#include "jwt.h" +#include "AWSIAMauth.h" +#include "../common/AWSIAMtrace.h" + +#define MAX_STR_LEN 8192 + +/**************************************************************************** +* +* Function Name = get_json_object_from_jwt +* +* Descriptive Name = Parse and tokenize JWT to return json_object from it +* +* Dependencies = +* +* Restrictions = +* +* Input = jwt/jwt_len - JSON Web Token +* logFunc - A pointer to the db2secLogMessage API +* for saving debugging messages in db2diag.log +* +* Output = json_object from given JWT - jwt_obj +* +* Normal Return = JWK_OK +* +* Error Return = JWT_ERR, JWT_RSA_ERR, JWT_RSA_KEY_ERR, etc. +* +*****************************************************************************/ +OM_uint32 get_json_object_from_jwt(const char *jwt, size_t jwt_len, json_object** jwt_obj, db2secLogMessage *logFunc) +{ + char *start = NULL, *end = NULL; + assert(jwt); + char* json_body = NULL; + size_t json_len = 0; + json_tokener* tok = NULL; + int ret = JWK_OK; + + /* decode body */ + start = memchr(jwt, '.', jwt_len); + if( !start ) + { + ret = JWT_ERR; + goto exit; + } + ++start; + + end = memchr(start, '.', jwt_len - (start - jwt)); + if( !end ) + { + ret = JWT_ERR; + goto exit; + } + + json_len = base64_decode(start, end - start, (unsigned char **)&json_body); + if( !json_body ) + { + ret = JWT_ERR; + goto exit; + } + + tok = json_tokener_new(); + if( !tok ) + { + ret = JWT_ERR; + goto exit; + } + + *jwt_obj = json_tokener_parse_ex(tok, json_body, json_len); + if (*jwt_obj == NULL) + { + ret = JWK_JSONTOKEN_ERR; + goto exit; + } + + return ret; + +exit: + if(json_body) free(json_body); + if(tok) json_tokener_free(tok); + return ret; +} +/**************************************************************************** +* +* Function Name = verify_signature +* +* Descriptive Name = Validate JWT to have all the parameters in +* proper conditions +* +* Dependencies = +* +* Restrictions = +* +* Input = jwt/jwt_len - JSON Web Token +* BIGNUM mod/exp - A BIGNUM pointer to hold +* exponential and modulus of RSA key +* THESE WILL BE DELETED +* logFunc - A pointer to the db2secLogMessage API +* for saving debugging messages in db2diag.log +* +* Output = integer - Return code +* +* Normal Return = JWK_OK +* +* Error Return = JWT_ERR, JWT_RSA_ERR, JWT_RSA_KEY_ERR, etc. +* +*****************************************************************************/ +int verify_signature(const char *jwt, size_t jwt_len, BIGNUM *mod, BIGNUM *exp, db2secLogMessage *logFunc) +{ + char *message = NULL; + char *signature_encoded = NULL; + size_t signature_len = 0; + unsigned char *signature = NULL; + int rc = JWK_OK; + RSA *rsa = NULL; + EVP_PKEY *key = NULL; + EVP_MD_CTX *ctx = NULL; + + if ( jwt ) + { + message = strndup(jwt, jwt_len); + } + + if ( !message ) + { + rc = JWT_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_signature: ERROR in jwt string\n"); + goto exit; + } + + signature_encoded = strchr(message, '.'); + if ( !signature_encoded ) + { + rc = JWT_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_signature: ERROR in jwt string format\n"); + goto exit; + } + signature_encoded = strchr(signature_encoded + 1, '.'); + if ( !signature_encoded ) + { + rc = JWT_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_signature: ERROR in jwt string format\n"); + goto exit; + } + *signature_encoded++ = 0; + + signature_len = base64_decode(signature_encoded, + strnlen(signature_encoded, MAX_STR_LEN), + &signature); + if ( signature_len == 0 || !(signature) ) + { + rc = JWT_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_signature: ERROR decoding jwt signature\n"); + goto exit; + } +#ifdef DBG + FILE *fp = fopen("/tmp/signature", "wb"); + fwrite(signature, signature_len, 1, fp); + fclose(fp); + fp = fopen("/tmp/message", "wb"); + fwrite(message, strlen(message), 1, fp); + fclose(fp); +#endif + + rsa = RSA_new(); + if ( !rsa ) + { + rc = JWT_RSA_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_signature: ERROR creating RSA Object\n"); + goto exit; + } + +#if defined(OPENSSL_1_1_0) + if (RSA_set0_key(rsa, mod, exp, NULL) != 1) + { + rc = JWT_RSA_KEY_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_signature: ERROR setting RSA key components\n"); + goto exit; + } +#else + rsa->n = mod; + rsa->e = exp; +#endif + + // Set these pointers to NULL as they are handled via RSA_free instead + // after this point + mod = NULL; + exp = NULL; + + key = EVP_PKEY_new(); + if ( !key || EVP_PKEY_set1_RSA(key, rsa) != 1 ) + { + rc = JWT_RSA_PRV_KEY_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_signature: ERROR setting RSA Private key\n"); + goto exit; + } + + ctx = EVP_MD_CTX_create(); + if ( !ctx ) + { + rc = JWT_PRV_KEY_CTX_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_signature: ERROR creating RSA Private key digest context(ctx) \n"); + goto exit; + } + + if ( EVP_DigestVerifyInit(ctx, NULL, EVP_sha256(), NULL, key) != 1 ) + { + rc = JWT_PRV_KEY_CTX_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_signature: ERROR setting up RSA Private key digest context(ctx) \n"); + goto exit; + } + if ( EVP_DigestVerifyUpdate(ctx, message, strnlen(message, + MAX_STR_LEN)) != 1 ) + { + rc = JWT_PRV_KEY_CTX_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_signature: ERROR hashing data with RSA Private key digest context(ctx) \n"); + goto exit; + } + if ( EVP_DigestVerifyFinal(ctx, signature, signature_len) != 1 ) + { + rc = JWT_PRV_KEY_CTX_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_signature: ERROR failure to verify data in RSA Private key digest context(ctx) against the signature\n"); + goto exit; + } + +exit: + if ( signature ) + { + free(signature); + } + if ( ctx ) + { + EVP_MD_CTX_destroy(ctx); + } + if ( key ) + { + EVP_PKEY_free(key); + } + if ( rsa ) + { + RSA_free(rsa); + } + + // This function has the responsibility to delete mod and exp + if(NULL != mod) + { + BN_free(mod); + } + if(NULL != exp) + { + BN_free(exp); + } + + if ( message ) + { + free(message); + } + return rc; +} + +/****************************************************************************** +* +* Function Name = verify_jwt_header_and_signature +* +* Descriptive Name = Validate JWT header format and its signature +* +* Dependencies = +* +* Restrictions = +* +* Input = jwt/jwt_len - JSON Web Token +* logFunc - A pointer to the db2secLogMessage API +* for saving debugging messages in db2diag.log +* +* Output = Return code of type OM_uint32 +* +* Normal Return = JWK_OK +* +* Error Return = JWK_EXP_ERR, JWK_MOD_ERR, JWT_BIGNUM_ERR, etc. +* +******************************************************************************/ +OM_uint32 verify_jwt_header_and_signature(const char *jwt, size_t jwt_len, char* iss, db2secLogMessage *logFunc) +{ + unsigned char *mod_bytes = NULL, *exp_bytes = NULL; + size_t mod_len = 0, exp_len = 0; + BIGNUM *mod_bn=NULL, *exp_bn=NULL; + json_bool keyExists = FALSE; + json_object *exp_obj = NULL; + json_object *mod_obj = NULL; + int rc = JWK_OK; + + json_object *jwk = NULL; + mod_bn = BN_new(); + if(mod_bn == NULL) + { + rc = JWT_NO_BN_NEW_FAILED; + goto exit; + } + + exp_bn = BN_new(); + if(exp_bn == NULL) + { + rc = JWT_NO_BN_NEW_FAILED; + goto exit; + } + + rc = get_jwk(jwt, jwt_len, iss, &jwk, logFunc); + if ( rc != 0 ) + { + goto exit; + } + + keyExists = json_object_object_get_ex(jwk, "e", &exp_obj); + if( !keyExists ) + { + rc = JWK_EXP_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_jwt_header_and_signature: encryption exponent e not found in jwk"); + goto exit; + } + + keyExists = json_object_object_get_ex(jwk, "n", &mod_obj); + if( !keyExists ) + { + rc = JWK_MOD_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_jwt_header_and_signature: modulus component n not found in jwk"); + goto exit; + } + + mod_len = base64_decode(json_object_get_string(mod_obj), + json_object_get_string_len(mod_obj), + &mod_bytes); + if (mod_len == 0) + { + rc = JWK_MOD_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_jwt_header_and_signature: Error in modulus component of jwk, mod_len is 0"); + goto exit; + } + exp_len = base64_decode(json_object_get_string(exp_obj), + json_object_get_string_len(exp_obj), + &exp_bytes); + if (exp_len == 0) + { + rc = JWK_EXP_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_jwt_header_and_signature: Error in encryption exponent of jwk, exp_len is 0"); + goto exit; + } + + if ( !BN_bin2bn(mod_bytes, mod_len, mod_bn) ) + { + rc = JWT_BIGNUM_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_jwt_header_and_signature: BIGNUM conversion failed for mod_len"); + goto exit; + } + + if ( !BN_bin2bn(exp_bytes, exp_len, exp_bn) ) + { + rc = JWT_BIGNUM_ERR; + db2Log( DB2SEC_LOG_ERROR, "verify_jwt_header_and_signature: BIGNUM conversion failed for exp_len"); + goto exit; + } + + rc = verify_signature(jwt, jwt_len, mod_bn, exp_bn, logFunc); + + // mod_bn and exp_bn are BN_free'd when RSA_free is called in verify_signature + // See crypto/rsa/rsa_lib.c#L128 + // Set these to NULL so we don't double free + mod_bn = NULL; + exp_bn = NULL; + +exit: + if(NULL != mod_bn) + { + BN_free(mod_bn); + } + + if(NULL != exp_bn) + { + BN_free(exp_bn); + } + + if ( jwk ) + { + json_object_put(jwk); + } + + if( rc != 0 ) + { + db2Log( DB2SEC_LOG_ERROR, "verify_jwt_header_and_signature: Fail to get public key, rc = %d\n", rc ); + } + return rc; +} + +/****************************************************************************** +* +* Function Name = get_string_element_from_jwt +* +* Descriptive Name = Get Value for given element_name from JWT object +* +* Dependencies = +* +* Restrictions = +* +* Input = jwt_obj - json_object* +* element_name - Name of the key element of the JWT object +* resMaxLen - the maximum length of element content. +* +* Output = res - element content +* +* Normal Return = element length which is greater than 0 +* +* Error Return = 0 +* +*******************************************************************************/ +size_t get_string_element_from_jwt(const json_object* jwt_obj, const char * const element_name, char **res, size_t resMaxLen) +{ + json_bool keyExists = FALSE; + json_object *element_obj = NULL; + size_t resLen = 0; + + const char* element = NULL; + int elementLen = 0; + + assert(element_name); + assert(jwt_obj); + + keyExists = json_object_object_get_ex(jwt_obj, element_name, &element_obj); + + if( !keyExists || !json_object_is_type(element_obj, json_type_string) ) + { + printf("\n %s key of type string is not found \n", element_name); + return 0; + } + + // Get the element content + element = json_object_get_string(element_obj); + elementLen = json_object_get_string_len(element_obj); + + if( elementLen > 0 && elementLen <= resMaxLen ) + { + *res = (char*)malloc( (elementLen+1) * sizeof(char)); + strcpy(*res, element); + (*res)[elementLen] = '\0'; + resLen = elementLen; + } + + return resLen; +} + +/****************************************************************************** +* +* Function Name = get_int64_element_from_jwt +* +* Descriptive Name = Get integer value of given element_name from JWT +* +* Dependencies = +* +* Restrictions = +* +* Input = jwt_obj - json_object* +* element_name - Name of the key element of the JWT object +* +* Output = element value of type db2Uint64 +* +* Normal Return = element value +* +* Error Return = 0 if no conversion exists +* +*******************************************************************************/ +db2Uint64 get_int64_element_from_jwt(const json_object* jwt_obj, const char * const element_name ) +{ + json_bool keyExists = FALSE; + json_object *element_obj; + + db2Uint64 element = 0; + + assert(jwt_obj); + assert(element_name); + + keyExists = json_object_object_get_ex(jwt_obj, element_name, &element_obj); + if( keyExists && json_object_is_type(element_obj, json_type_int) ) + { + element = json_object_get_int64(element_obj); + } +} + +/****************************************************************************** +* +* Function Name = jwt_get_payload_array_element +* +* Descriptive Name = Get JSON object from JWT associated with key element +* +* Dependencies = +* +* Restrictions = +* +* Input = jwt_obj - json_object* +* element_name - Name of the key element of the JWT object +* +* Output = res - element content +* +* Normal Return = element length which is greater than 0 +* +* Error Return = 0 +* +*******************************************************************************/ +char** jwt_get_payload_array_element(const json_object* jwt_obj, const char * const element_name, size_t* resLen) +{ + json_bool keyExists = FALSE; + json_object *element_arr = NULL ; + + char** res = NULL; + assert(jwt_obj); + assert(element_name); + + keyExists = json_object_object_get_ex(jwt_obj, element_name, &element_arr); + if( !keyExists || !json_object_is_type(element_arr, json_type_array) ) + { + db2Log( DB2SEC_LOG_ERROR, "jwt_get_payload_array_element: Failed to retrieve array object for %s \n", element_name ); + goto finish; + } + + *resLen = json_object_array_length(element_arr); + + res = (char **)malloc(*resLen * sizeof(char *)); + // Loop through array of keys and get the required values. + for (size_t i = 0; i < *resLen; ++i) + { + json_object *element = json_object_array_get_idx(element_arr, i); + if ( !element ) + { + continue; + } + const char* group = json_object_get_string(element); + res[i] = (char *)malloc((strlen(group)+1) * sizeof(char)); + memset(res[i], '\0', strlen(group)+1); + memcpy(res[i], group, strlen(group)); + } + return res; + +finish: + return NULL; +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/jwt.h b/aws/security_plugins/db2-aws-iam/src/gss/jwt.h new file mode 100644 index 0000000..35b2ed2 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/jwt.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/jwt.h (%W%) +** +** Descriptive Name = JSON Web Token operation code +** +** Function: This code contains functions that retrieves various types of data from given json object. + It also has functions that validates header and signature of the JWT. +** +** +*****************************************************************************/ + +#ifndef _JWT_H_ +#define _JWT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +int verify_signature(const char *jwt, size_t jwt_len, BIGNUM *mod, BIGNUM *exp, db2secLogMessage *logFunc); +OM_uint32 verify_jwt_header_and_signature(const char *jwt, size_t jwt_len, char* iss, db2secLogMessage *logFunc); + +OM_uint32 get_json_object_from_jwt(const char *jwt, size_t jwt_len, json_object** jwt_obj, db2secLogMessage *logFunc); +size_t get_string_element_from_jwt(const json_object* jwt_obj, const char * const element_name, char **res, size_t resMaxLen); +db2Uint64 get_int64_element_from_jwt(const json_object* jwt_obj, const char * const element_name); +char** jwt_get_payload_array_element(const json_object* jwt_obj, const char * const element_name, size_t* resLen); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/aws/security_plugins/db2-aws-iam/src/gss/users.json b/aws/security_plugins/db2-aws-iam/src/gss/users.json new file mode 100644 index 0000000..7dc1dfa --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/users.json @@ -0,0 +1,40 @@ +{ + "iamusers": { + "IBMid-1900097PYX": "dv_ibmid_1900097pyx" + }, + "policies": { + "default": { + "max_attempts": "5", + "lock_duration": "900" + }, + "db2inst1_policy": { + "max_attempts": "5", + "lock_duration": "0" + } + }, + "groups": { + "BLUUSERS": { + "desc": "Non-Admin Group" + }, + "DB2IADM1": { + "desc": "SYSADM group" + }, + "BLUADMIN": { + "desc": "Admin Group" + } + }, + "users": { + "dv_ibmid_1900097pyx": { + "username": "dv_ibmid_1900097pyx", + "role": "DV_ADMIN", + "email": "json.test@ibm.com", + "policyname": "default", + "locked": false, + "locked_count": 0, + "locked_time": 0, + "password": "{SHA2}VK2P5JkVuB8RHZ2gDJFQ60sj3iHmetRr2oD\/92NKvuYwSSpLpICEqQ==", + "group": "bluusers" + } + }, + "rev": 0 +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/utils.c b/aws/security_plugins/db2-aws-iam/src/gss/utils.c new file mode 100644 index 0000000..0de4f73 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/utils.c @@ -0,0 +1,107 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/utils.cpp (%W%) +** +** Descriptive Name = Implementation of a few utility functions +** +** +** +***********************************************************************/ +#include +#include +#include +#include +#include "../common/AWSIAMtrace.h" +#include "utils.h" +#include "AWSIAMauth.h" + +void stringToLower(char *s) +{ + int i=0; + while(s[i]!='\0') + { + if(s[i]>='A' && s[i]<='Z'){ + s[i]=s[i]+32; + } + ++i; + } +} + +void stringToUpper(char *s) +{ + int i=0; + while(s[i]!='\0') + { + if(s[i]>='a' && s[i]<='z'){ + s[i]=s[i]-32; + } + ++i; + } +} + + +const char* read_userpool_from_cfg() +{ + char* userpoolCfgFile = NULL; + struct json_object *parsed_json = NULL, *userpool_json = NULL; + char* cfgPathEnv = getenv("AWS_USERPOOL_CFG_ENV"); + char *db2_home = getenv( "DB2_HOME" ); + const char* userpoolID = NULL; + if(cfgPathEnv == NULL) + { + cfgPathEnv = AWS_USERPOOL_DEFAULTCFGFILE; + } + if(db2_home != NULL) + { + userpoolCfgFile = (char*) malloc(sizeof(char) * (strlen(cfgPathEnv) + strlen(db2_home) + 2 )); + if(userpoolCfgFile != NULL) + { + strcpy(userpoolCfgFile, db2_home); + strcat(userpoolCfgFile, "/"); + strcat(userpoolCfgFile, cfgPathEnv); + userpoolCfgFile[strlen(userpoolCfgFile)] = '\0'; + } + } + if(userpoolCfgFile != NULL) + { + parsed_json = json_object_from_file(userpoolCfgFile); + if (parsed_json == NULL) + { + db2Log(DB2SEC_LOG_ERROR, "No userpool configured"); + IAM_TRACE_DATA( "read_userpool_from_cfg", "10"); + goto exit; + } + + if(!json_object_object_get_ex(parsed_json, "UserPools", &userpool_json)) + { + IAM_TRACE_DATA("read_userpool_from_cfg","20"); + goto exit; + } + json_object* id = NULL; + bool found = json_object_object_get_ex(userpool_json, "ID", &id); + if ( !found ) + { + goto exit; + } + userpoolID = json_object_get_string(id); + return userpoolID; + } + goto exit; + +exit: + if(parsed_json) json_object_put(parsed_json); + if(userpool_json) json_object_put(userpool_json); + return userpoolID; +} diff --git a/aws/security_plugins/db2-aws-iam/src/gss/utils.h b/aws/security_plugins/db2-aws-iam/src/gss/utils.h new file mode 100644 index 0000000..4128c6c --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/gss/utils.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** Licensed Materials - Property of IBM +** +** Governed under the terms of the International +** License Agreement for Non-Warranted Sample Code. +** +** (C) COPYRIGHT International Business Machines Corp. 2024 +** All Rights Reserved. +** +** US Government Users Restricted Rights - Use, duplication or +** disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +** +********************************************************************************** +** +** Source File Name = src/gss/utils.h (%W%) +** +** Descriptive Name = Header file for a few utility functions +** +** +** +***********************************************************************/ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include + + +#define AWS_USERPOOL_CFG_ENV +#define AWS_USERPOOL_DEFAULTCFGFILE "security64/plugin/cfg/cognito_userpools.json" +#define DB2OC_USER_REGISTRY_FILE "/mnt/blumeta0/db2_config/users.json" +#define DB2OC_USER_REGISTRY_ERROR_FILE "/mnt/blumeta0/db2_config/users.json.debug" + +void stringToUpper(char *s); + +void stringToLower(char *s); + +#ifdef __cplusplus +extern "C" { +#endif + +const char* read_userpool_from_cfg(); + +#ifdef __cplusplus +} +#endif + +#endif //_UTILS_H_ diff --git a/aws/security_plugins/db2-aws-iam/src/test/Makefile b/aws/security_plugins/db2-aws-iam/src/test/Makefile new file mode 100755 index 0000000..81c364e --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/test/Makefile @@ -0,0 +1,96 @@ +.DEFAULT_GOAL := all + +PRINCIPAL_NAME := AWSIAMauth +GSSAPI_CLIENT_MODULE := $(PRINCIPAL_NAME)client.so +GSSAPI_SERVER_MODULE := $(PRINCIPAL_NAME)server.so +GROUP_MODULE := $(PRINCIPAL_NAME)group.so + +AWS_SDK_LIBS := -L/usr/local/lib64 -laws-cpp-sdk-core -laws-cpp-sdk-cognito-idp -Wl,-rpath,/usr/local/lib64 +CPPLIBS := -lstdc++ + +CXXFLAGS := -D_GLIBCXX_USE_CXX11_ABI=0 + +INCLUDES := -I$(DB2_HOME)/include -I../gss -I../common -I/usr/include +OSSL_LIBS_PATH := -L/usr/lib64 + +ifeq ($(OPENSSL_VER), 3) + INCLUDES := -I$(DB2_HOME)/include -I../gss -I../common -I/usr/include -I/usr/include/openssl3 +endif + +# Check the installed version of openssl and json_c and according set the macro +INSTALLED_OPENSSL := $(shell yum info installed openssl | grep Version | sed -e 's/Version\s*:\s*//g' | sed -e 's/[a-z]-*.*//' | sed -e 's/\.//g') +INSTALLED_JSON_C := $(shell yum info installed json-c | grep Version | sed -e 's/Version\s*:\s*//g' | sed -e 's/\.//g') +$(info INSTALLED_OPENSSL is $(INSTALLED_OPENSSL)) +$(info INSTALLED_JSON_C is $(INSTALLED_JSON_C)) + +OSSL_MACRO := +VER_GT_110 = $(shell echo $(INSTALLED_OPENSSL)\>=110 | bc ) +ifeq ($(VER_GT_110), 1) +OSSL_MACRO += -DOPENSSL_1_1_0 +endif + +JSON_MACRO := +VER_GT_0_13 = $(shell echo $(INSTALLED_JSON_C)\>=0130 | bc ) +ifeq ($(VER_GT_0_13), 1) +JSON_MACRO += -DJSON_C_0_13 +endif + +CFLAGS := $(INCLUDES) -L$(DB2_HOME)/lib -L../obj -g -fpic -fno-strict-aliasing -Werror \ + -DSQLUNIX \ + -D_GLIBCXX_USE_CXX11_ABI=0 \ + -D_REENTRANT \ + $(OSSL_MACRO) \ + $(JSON_MACRO) \ + -DCRYPT_OPENSSL_SUPPORTED + +SCFLAGS := -I./$(DB2_HOME)/include -I./../gss -L$(DB2_HOME)/lib -L.. -fpic -fno-strict-aliasing + +%.o: %.c %.h + $(CC) $(CFLAGS) -c $< -o $@ + + +LIBS := -lcrypto -lcurl -ljson-c $(OSSL_LIBS_PATH) +GROUP_LIBS := -lcrypto -Wl,-z,defs +GROUP_LIBSJ := -ljson-c +SERVERLIBS := -lssl -ldb2 + + +unitTests: unit_jwk.o unit_jwt.o unit_aws_sdk_apis.o unit_main.o + g++ $(CFLAGS) $(LIBS) $(AWS_SDK_LIBS) -o $@ unit_jwk.o unit_jwt.o unit_aws_sdk_apis.o unit_main.o ../build/security64/plugin/IBM/server/AWSIAMauth.so ../build/security64/plugin/IBM/group/AWSIAMauthgroup.so + +jwtTests: unit_jwt.o unit_main.o + g++ $(CFLAGS) $(LIBS) -o $@ unit_jwt.o unit_main.o ../build/security64/plugin/IBM/server/AWSIAMauth.so ../build/security64/plugin/IBM/group/AWSIAMauthgroup.so + +jwkTests: unit_jwk.o unit_main.o + g++ $(CFLAGS) $(LIBS) -o $@ unit_jwk.o unit_main.o ../build/security64/plugin/IBM/server/AWSIAMauth.so ../build/security64/plugin/IBM/group/AWSIAMauthgroup.so + +awsTests: unit_aws_sdk_apis.o unit_main.o + g++ $(CFLAGS) $(CXXFLAGS) $(LIBS) $(AWS_SDK_LIBS) -o $@ unit_aws_sdk_apis.o unit_main.o ../build/security64/plugin/IBM/server/AWSIAMauth.so ../build/security64/plugin/IBM/group/AWSIAMauthgroup.so + +fullTest: fulltest.o + g++ $(CFLAGS) $(CXXFLAGS) $(LIBS) $(AWS_SDK_LIBS) -o $@ fulltest.o ../build/security64/plugin/IBM/server/AWSIAMauth.so ../build/security64/plugin/IBM/group/AWSIAMauthgroup.so + +unit_jwt.o: unit_jwt.cpp + g++ $(CFLAGS) $(LIBS) -c -o $@ $< + +unit_jwk.o: unit_jwk.cpp + g++ $(CFLAGS) $(LIBS) -c -o $@ $< + +unit_aws_sdk_apis.o: unit_aws_sdk_apis.cpp + g++ $(CFLAGS) $(CXXFLAGS) -I/usr/local/include $(CPPLIBS) $(AWS_SDK_LIBS) $(LIBS) -c -o $@ $< + +# Command line example +unit_main.o: unit_main.cpp + g++ $(CFLAGS) $(CXXFLAGS) $(LIBS) -c -o $@ $< + +fulltest.o: fulltest.c + g++ $(CFLAGS) $(LIBS) -c -o $@ $< + +.PHONY: all clean +all: unitTests awsTests jwkTests jwtTests fullTest + +clean: + rm -f *.o $(PROGS) jwtTests jwkTests awsTests unitTests fullTest + +cleansetup: + rm -f env.sh test_cognito_userpools.json diff --git a/aws/security_plugins/db2-aws-iam/src/test/README.md b/aws/security_plugins/db2-aws-iam/src/test/README.md new file mode 100644 index 0000000..2ccb4e1 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/test/README.md @@ -0,0 +1,76 @@ +# Unit Tests + +The unittest setup actually creates a userpool, groups, users, etc. in the AWS Cognito. And once the tests are executed, the test scripts also deletes the created userpool. The system/container where these tests are executed should have AWS developer credentials configured. In case of error, it may happen that tests fail to cleanup userpool in the AWS Cognito. Please make sure it is deleted after test execution. + + +## Install Dependencies + +Install following dependencies in order to execute the tests - + +1. Install AWS CLI from https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html. Note that installing `awscli` using yum gives you an older version of AWS CLI which lacks many new features which are needed for these tests. So, it is recommended to use following way of installing AWS CLI. + +```shell +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" +unzip awscliv2.zip +sudo ./aws/install +``` + +2. Install python package, if not already present. Tests are tested with python 2.7 as well as 3.11. + +```shell +sudo yum install python3.11 +sudo ln -s /usr/bin/python3.11 /usr/bin/python +``` + +## Build the tests + +Make sure the plugin code is built before running following command. + +```shell +export INSTALLED_OPENSSL=$(openssl version | awk '{print $2}' | sed -e 's/[a-z]-*.*//' | awk -F. '{ print $1$2$3 }') +export INSTALLED_JSON_C=$(yum info installed json-c | grep Version | sed -e 's/Version\s*: //g' | awk -F. '{ print $1$2$3 }') + +make clean && make all +``` + +## Run the tests + +The unit tests written depend on the cognito setup like creating userpool, users, groups, etc. This test directory contains a few scripts to create those entities in AWS cognito and use those to execute the tests successfully. +1. `settings.sh` - This script contains the static or default settings needed by setup and tear down scripts. +2. `setup_cognito_for_tests.sh` - This script creates a userpool, creates two groups, creates one user, assigns groups to the user and dumps the generated information like USERPOOLID, CLIENTID, USERNAME_GENERATED in the `env.sh`. This script also creates a file with a name as set by variable `AWS_USERPOOL_CFG_ENV` in settings.sh +3. `teardown_cognito.sh` - This script deletes the AWS cognito userpool and removes the temporary generated files. +4. `retrieve_token.sh` - This script is used to retrieve the ID token for the test user. It is run as part of `setup_cognito_for_tests.sh` script and it dumps the generated token in `env.sh`. +5. `testAll.sh` - This script does everything i.e. it runs setup script, execute the tests, and tear down the userpool. + +Some temporary output files generated by above scripts - +1. `env.sh` - This is a temporary file used to setup environment variables needed by unit tests. +2. `test_cognito_userpools.json` - This script contains userpoolID and clientID which is created in AWS and it is used by plugin code. The name of this file can be changed by updating `settings.sh`. +Make sure that above two temporary files are not commited to the repo. + + +```shell +./testAll.sh +``` + +The `testAll.sh` script needs a password to be provided which will be set for a test user created in userpool. It can be anything that satifies the cognito password requirements. + +In case the tests fail and for some reason cognito userpool created as part of test setup remains undeleted, please run below scripts - + +```shell +source ./env.sh +./teardown_cognito.sh +``` + +# Full Test of Token Validation +There is a test file written `fulltest.c` which tests the entire flow of token validation as it is done in server side AWS IAM plugin. + +This test can be individually built and run as follows - + +```shell +make fullTest +./fullTest $ACCESSTOKEN +``` + +where `ACCESSTOKEN` is the ID/access token retrieved from AWS Cognito for a user who is part of userpool. + +Note: Any change done in the token validation function `validate_auth_info` in server side plugin (`AWSIAMauthserver.c`) should be made in fulltest.c file too. diff --git a/aws/security_plugins/db2-aws-iam/src/test/catch.hpp b/aws/security_plugins/db2-aws-iam/src/test/catch.hpp new file mode 100644 index 0000000..7e706f9 --- /dev/null +++ b/aws/security_plugins/db2-aws-iam/src/test/catch.hpp @@ -0,0 +1,17959 @@ +/* + * Catch v2.13.7 + * Generated: 2021-07-28 20:29:27.753164 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 13 +#define CATCH_VERSION_PATCH 7 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html +#ifdef __APPLE__ +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) && !defined(__CUDACC__) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ +# endif + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # include + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept { return file[0] == '\0'; } + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } + + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; + + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; + + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; + + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template