{"id":28714917,"url":"https://github.com/keeper-security/discovery-and-rotation-saas-dev","last_synced_at":"2026-04-07T05:31:17.144Z","repository":{"id":293533545,"uuid":"937027397","full_name":"Keeper-Security/discovery-and-rotation-saas-dev","owner":"Keeper-Security","description":"Environment to create and test KeeperPAM credential rotation plugins","archived":false,"fork":false,"pushed_at":"2025-09-14T01:23:47.000Z","size":24266,"stargazers_count":0,"open_issues_count":0,"forks_count":2,"subscribers_count":6,"default_branch":"main","last_synced_at":"2026-01-20T07:58:26.668Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://docs.keeper.io/en/keeperpam/privileged-access-manager/password-rotation","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Keeper-Security.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-02-22T06:43:57.000Z","updated_at":"2025-09-14T01:23:31.000Z","dependencies_parsed_at":"2025-06-04T10:36:59.914Z","dependency_job_id":"b4373fc6-3f5f-480d-87f7-3dc02a5d6877","html_url":"https://github.com/Keeper-Security/discovery-and-rotation-saas-dev","commit_stats":null,"previous_names":["keeper-security/discovery-and-rotation-saas-dev"],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/Keeper-Security/discovery-and-rotation-saas-dev","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Keeper-Security%2Fdiscovery-and-rotation-saas-dev","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Keeper-Security%2Fdiscovery-and-rotation-saas-dev/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Keeper-Security%2Fdiscovery-and-rotation-saas-dev/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Keeper-Security%2Fdiscovery-and-rotation-saas-dev/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Keeper-Security","download_url":"https://codeload.github.com/Keeper-Security/discovery-and-rotation-saas-dev/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Keeper-Security%2Fdiscovery-and-rotation-saas-dev/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31501903,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-15T01:41:43.389Z","updated_at":"2026-04-07T05:31:17.129Z","avatar_url":"https://github.com/Keeper-Security.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"![KeeperPAM Rotation Plugin Development Environment Header](https://github.com/user-attachments/assets/d56d224f-ffc4-48d3-a330-3ac3f08f46c0)\n\n# KeeperPAM SaaS Rotation Plugin Development Environment\n\nThis is the development environment to build and test plugins for\nKeeperPAM automated credential rotations.\n\n## Documentation\n\nSee: [KeeperPAM SaaS Rotation Plugin Documentation](https://docs.keeper.io/en/keeperpam/privileged-access-manager/password-rotation/rotation-use-cases/saas-plugins)\n\n## Setup\n\nCurrently, the setup guide is focused on Linux and macOS. \n\n### Get the Development Environment\n\nIt is assumed that Python 3.8, or greater, has been installed on your system.\nThe setup will create a Python virtual environment that needs to be activated before\n  working on the plugin.\n\n```shell\npython -m venv venv\n. ./venv/bin/activate\npip install --upgrade pip\ngit clone git@github.com:Keeper-Security/discovery-and-rotation-saas-dev.git\ncd discovery-and-rotation-saas-dev\npip install .\ncd ..\n```\n\n### Plugin Structure\n\n#### Work Directory\n\nCreate a directory in your preferred location. \nThe directory needs to be named the same as your plugin.\nIn the example below, the touch will create an empty `my_plugin.py` file.\nThis is where the Python plugin will be created and edited.\n\n```shell\nmkdir my_plugin\ncd my_plugin\ntouch my_plugin.py\n```\n\nNext you need a Keeper Secrets Manager configuration.\n\n* Have, or create, an Application using the Vault's Secret Manager. \n  The Application being used for a Gateway will work.\n* Remember a Shared Folder UID using in the Application. \n  Click on the folder, and then click on the **ⓘ**, and then click on the **UID** value.\n* For the Application, add a new device on the Devices tab. \n  For the Method, select Configuration File and JSON, and then click the Download button.\n  This will download `config.json`. \n* Moved `config.json` to your work directory, or you can specify the path using \n  `--config` for the commands.\n\n```shell\ncp /path/to/downloaded/config.json .\n```\n\n#### Unit Test\n\nTests are required.\nThere is a minimum limit of 70% coverage.\nAny unit test in the work directory will be run when a PR is created.\n\nIf special modules are needed for testing. They can be included in `requirements_test.txt`.\n\n#### Project Structure\n\nThe final structure should look like this.\n\n```script\n% tree\n.\n└── my_plugin\n    ├── config.json\n    ├── my_plugin_test.py\n    ├── my_plugin.py\n    ├── README.md\n    └── requirements_test.txt\n```\nThe `config.json` is in the `.gitignore`. \nHowever, if you managed to add it to the repo, the PR validation will fail.\n\n## Test with Hello World\n\nCopy the `hello_world.py` file from the `examples` directory to your work directory. \n\n```shell\ncd /path/to/my_work_dir/hello_world\ncp /path/to/discovery-and-rotation-saas-dev/exmaples/hello_world.py .\n```\n\n### SaaS Config Record\n\nCurrently, the SaaS Config record is a **Login** record with custom fields that are\n  used to configure the SaaS rotation.\n\nThe command `plugin_test config` is used to make a SaaS Config record in your Vault.\nIt will prompt you to enter required and optional values.\n\n```shell\n(venv) user@machine:~$ plugin_test config --help\nUsage: plugin_test config [OPTIONS]\n\n  Create a config file\n\nOptions:\n  -f, --file TEXT               Plugin python file  [required]\n  -s, --shared-folder-uid TEXT  Shared folder UID  [required]\n  -t, --title TEXT              SaaS config record tile  [required]\n  --config TEXT                 KSM configuration file\n  --help                        Show this message and exit.\n```\n#### Required\n* `-f`, `--file` = The Python file\n* `-s`, `--shared-folder-uid` = The Shared Folder UID for your Application where \n                               you want to create the SaaS Config record.\n* `-t`, `--title` = The title for the SaaS Config record.\n#### Optional\n*  `--config` = Path to KSM Configuration JSON, if not in the work directory.\n\nHere is an example of the command being run.\n\n```shell\n(venv) user@machine:~$ plugin_test config -f hello_world.py -t \"Hello World Config\" -s XXXX\nRequired: My Message\nThis is the message that will be displayed. The field is required.\nEnter Value : \u003e This is a required value\n\nOptional: My Optional\nThis is an optional field, but is secret if it exists\nEnter Value  (default: This is a secret): \u003e This is optional, and a secret.\n\nConfiguration record UID is YYYY\n```\nA **record UID** is displayed at the end. This will be needed when using the `plugin_test run`\ncommand.\n\n\nHere is what the record looks like in the Vault.\n\n![record.png](.images/config_record.png)\n\n### Test the Plugin\n\nThe following command wil run the plugin.\n\n```shell\n(venv) user@machine:~$ plugin_test run --help\nUsage: plugin_test run [OPTIONS]\n\n  Run the plugin\n\nOptions:\n  -f, --file TEXT               Plugin python file  [required]\n  -u, --user-uid TEXT           UID of PAM User record  [required]\n  -c, --plugin-config-uid TEXT  UID of plugin config record  [required]\n  --configuration-uid TEXT      UID of configuration record\n  --fail                        Force run to fail\n  --new-password TEXT           New password\n  --old-password TEXT           Old password\n  --no-old-password             Do not use old password\n  --config TEXT                 KSM configuration file\n  --help                        Show this message and exit.\n                 Show this message and exit.\n```\n#### Required\n* `-f`, `--file` = The Python file\n* `-u`, `--user-uid` = The record UID of a PAM User record.\n* `-c`, `--plugin-config-uid` = The record UID of the SaaS Config record.\n#### Optional\n* `--configuration-uid` - If the plugin uses AWS or Azure, the credentials from the\n                          configuration record can be supplied to the plugin by setting \n                          this param.\n* `--fail` = Force the plugin to fail password rotation.\n             This will trigger a rollback of the password change, if plugin supports\n             rollback.\n* `--new-password` = Manually set the new password.\n                     If not set, a random password will be generated.\n* `--old-password` = Manually set the old password.\n                     If not set, the password on the user record will be used.\n* `--no-old-password` = Make the old password blank. \n                        Do not read from user record.\n* `--config` = Path to KSM Configuration JSON, if not in the work directory.\n\nHere is an example of the command being run.\n\n```shell\n(venv) user@machine:~$ plugin_test run -f hello_world.py -u ZZZZ -c YYYY\n2025-04-23 22:41:00,809 kdnrm  INFO: starting rotating of the Hello World user\n2025-04-23 22:41:00,809 kdnrm  INFO: rotating the user in Hello World was a success\n2025-04-23 22:41:00,809 kdnrm  DEBUG: there were return custom fields\n2025-04-23 22:41:00,809 kdnrm  DEBUG: setting the return custom field 'Hello World Label' to value 'Hello there world!!!'\n2025-04-23 22:41:00,809 kdnrm  DEBUG: found existing 'Hello World Label' custom field in user record, updating type and value\n2025-04-23 22:41:00,809 kdnrm  DEBUG: updating the user record.\nRotation was successful\n```\nThe hello_world.py example sets return values.\nThese values are added to the PAM User record as custom fields.\n\n![user_record.png](.images/user_record.png)\n\n\n# Using the Keeper PAM Gateway\n\nIn order to use the plugin with the Gateway, a plugin directory needs to be created.\nTo tell this Gateway with directory exists the absolute path to the directory needs set on the\n  PAM configuration record.\nThis is done using the custom field `SaaS Plugins Dir`.\n\nCustom fields can be added to a PAM configuration record using \n  the [Commander CLI](https://docs.keeper.io/en/enterprise-guide/commander-cli).\nCurrently, the Vault can not add custom fields to a PAM configuration.\n\nThe Commander CLI command is\n```\nMy Vault\u003e record-update -r \u003cCONFIGURATION RECORD UID\u003e \"text.SaaS Plugins Dir=/path/to/dir\"\n```\n\nIf running the Keeper PAM Gateway inside a container, and the plugin directory is outside, you\nwill need to use `volumes` to map the external directory and internal directory.\n\nIn the example below, in the directory that contains the `docker-compose.yml` file, \n  the `outside_saas_plugins` is created.\nThis is the directory the SaaS plugin Python file can be copied.\nThis directory will be mounted inside the container at `/inside_saas_plugins`\n\n```dockerfile\nservices:\n  keeper-gateway:\n    platform: linux/amd64\n    image: keeper/gateway:preview\n    shm_size: 2g\n    security_opt:\n      - \"seccomp:docker-seccomp.json\"\n    restart: always\n    volumes:\n      - ./outside_saas_plugins:/inside_saas_plugins\n    environment:\n      ACCEPT_EULA: Y    # KCM Guacd Accept EULA\n      GATEWAY_CONFIG: XXXX\n```\n\nAnd then when setting the plugin directory, use the inside directory path.\n```\nMy Vault\u003e record-update -r \u003cCONFIGURATION RECORD UID\u003e \"text.SaaS Plugins Dir=/inside_saas_plugins\"\n```\n\n## Using PAM configuration information.\n\nIf your plugin uses AWS, Azure, etc. connection information from the PAM Configuration.\nBy default, you will not be able to access this information in the plugin unless included in list of\n  approved plugins.\n\nThis is controlled by the custom field `Allow SaaS Access`, which is a multiline fields.\nTo allow your plugin to access the PAM configuration information, the customer will need to\n  add the name of your plugin to this list.\n\n```\nMy Vault\u003e record-update -r \u003cCONFIGURATION RECORD UID\u003e \"multiline.Allow SaaS Access=My Plugin\\nMy Other Plugin\"```\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeeper-security%2Fdiscovery-and-rotation-saas-dev","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkeeper-security%2Fdiscovery-and-rotation-saas-dev","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeeper-security%2Fdiscovery-and-rotation-saas-dev/lists"}