{"id":13826330,"url":"https://github.com/threatstack/vpnnotify","last_synced_at":"2026-01-14T22:28:56.729Z","repository":{"id":89797085,"uuid":"101824655","full_name":"threatstack/vpnnotify","owner":"threatstack","description":"It tells you when you VPN","archived":false,"fork":false,"pushed_at":"2022-11-22T13:18:01.000Z","size":1627,"stargazers_count":13,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-11T00:05:33.875Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/threatstack.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}},"created_at":"2017-08-30T01:41:49.000Z","updated_at":"2023-09-05T13:55:25.000Z","dependencies_parsed_at":"2023-05-26T21:15:47.602Z","dependency_job_id":null,"html_url":"https://github.com/threatstack/vpnnotify","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/threatstack/vpnnotify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threatstack%2Fvpnnotify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threatstack%2Fvpnnotify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threatstack%2Fvpnnotify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threatstack%2Fvpnnotify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/threatstack","download_url":"https://codeload.github.com/threatstack/vpnnotify/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/threatstack%2Fvpnnotify/sbom","scorecard":{"id":883276,"data":{"date":"2025-08-11","repo":{"name":"github.com/threatstack/vpnnotify","commit":"53e80099ba3d751053f1e81d5ac9eede82e4fe49"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.7,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 0/5 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: BSD 3-Clause \"New\" or \"Revised\" License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact 2.1.0 not signed: https://api.github.com/repos/threatstack/vpnnotify/releases/18220659","Warn: release artifact 2.0.0 not signed: https://api.github.com/repos/threatstack/vpnnotify/releases/7566817","Warn: release artifact 2.1.0 does not have provenance: https://api.github.com/repos/threatstack/vpnnotify/releases/18220659","Warn: release artifact 2.0.0 does not have provenance: https://api.github.com/repos/threatstack/vpnnotify/releases/7566817"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 4 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T09:03:39.029Z","repository_id":89797085,"created_at":"2025-08-24T09:03:39.029Z","updated_at":"2025-08-24T09:03:39.029Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28436415,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T21:32:52.117Z","status":"ssl_error","status_checked_at":"2026-01-14T21:32:33.442Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6: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":"2024-08-04T09:01:35.755Z","updated_at":"2026-01-14T22:28:56.714Z","avatar_url":"https://github.com/threatstack.png","language":"Go","funding_links":[],"categories":["\u003ca id=\"d62a971d37c69db9f3b9187318c3921a\"\u003e\u003c/a\u003e工具"],"sub_categories":["\u003ca id=\"8ea8f890cf767c3801b5e7951fca3570\"\u003e\u003c/a\u003e公网访问局域网"],"readme":"# vpnnotify\n\n`vpnnotify` is a tool that will send a user a slack message when they connect\nto a VPN session. The goal of this tool is to let people know when connections\nare initiated on their behalf -- with the goal being that if a connection\noccurs without the user knowing, they could contact their CIRT.\n\n`vpnnotify` builds off of the support OpenVPN has for a client connect script.\nThis script will run when a client successfully connects to the VPN. OpenVPN\nexposes several environment variables for the script it runs to use. The ones\n`vpnnotify` currently uses are `common_name` (the login name) and `untrusted_ip`\n(the IP of the connection).\n\nThere are many improvements that could be made to `vpnnotify` -- as it is, we\nknow that this is fairly environment specific. We open sourced in hopes that we\ncould help level up other security operations teams and make another tool that\nwould contribute to the community.\n\n# Prerequisites\nIn order to use `vpnnotify` you'll need to have the following:\n\n* LDAP server with the slackUser object class\n* Redis to store last logged in state\n* OpenVPN - we've tested with 2.3.12 and later\n* FQDNs that have either `dev` or `prod` in them as an indicator of environment\n\n## slackUser Object Class\nThis adds a SlackName attribute to a record. If you use OpenLDAP with LDIF\nconfiguration, this schema LDIF will work:\n\n```\ndn: cn=slackuser,cn=schema,cn=config\nobjectClass: olcSchemaConfig\ncn: slackuser\nolcAttributeTypes: ( 1.3.6.1.4.1.48697.1.2.1.3.1 NAME 'slackName' DESC 'Slack\n  at-name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch\n  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE)\nolcObjectClasses: ( 1.3.6.1.4.1.48697.1.2.1.4.1 NAME 'slackUser' DESC 'Slack\n  User' SUP top AUXILIARY MUST (slackName) )\n```\n\n# Building and Installing\nBuilding `vpnnotify` is as easy as running `go build`. You could use\n[FPM](https://github.com/jordansissel/fpm) to make it into a package that could\ninstall to `/usr/bin`, if you wanted.\n\n# Configuration\n`vpnnotify` uses a configuration file which is, by default, in\n`/etc/vpnnotify.json`. You can override this with the `VPNNOTIFY_CONFIG`\nenvironment variable.\n\n## Example Configuration\nHere's a sample configuration file:\n\n```\n{\n  \"GeoIPEnabled\": true,\n  \"GeoIPPath\": \"/opt/GeoLite2-City_20170502/GeoLite2-City.mmdb\",\n  \"LDAPBaseDN\": \"dc=cool,dc=io\",\n  \"LDAPPort\": 389,\n  \"LDAPServer\": \"ldap.cool.io\",\n  \"LDAPSkipVerify\": false,\n  \"LDAPUserAttrib\": \"uid\",\n  \"RedisDB\": 0,\n  \"RedisPassword\": \"seCretsRc00l\",\n  \"RedisPort\": 6379,\n  \"RedisServer\": \"redis.cool.io\",\n  \"RedisTLS\": true,\n  \"RenotifyTime\": 7200,\n  \"SlackKey\": \"xozz-blah-moreblah\",\n  \"TemplatePath\": \"/etc/vpnnotify.msg\"\n}\n```\n## Configuration Attributes\n\n| Variable       | Type   | Purpose                                                    | Possible Value                   |\n|----------------|--------|------------------------------------------------------------|----------------------------------|\n| GeoIPEnabled   | Bool   | Whether to use GeoIP. Consult their license terms.         | true, false                      |\n| GeoIPPath      | String | Path to the GeoIP MMDB file                                | /opt/cooldb.mmdb                 |\n| LDAPBaseDN     | String | Base DN for your LDAP server                               | dc=cool,dc=io                    |\n| LDAPPort       | Int    | Port for the LDAP server (must use StartTLS)               | 389                              |\n| LDAPServer     | String | FQDN for LDAP server (must use StartTLS)                   | ldap.cool.io                     |\n| LDAPSkipVerify | Bool   | Whether to check the TLS certificate (for testing only!)   | false (you should keep it false) |\n| LDAPUserAttrib | String | The attribute by which to look up users                    | uid                              |\n| RedisDB        | Int    | The database number to use                                 | 0                                |\n| RedisPassword  | String | A password for Redis                                       | secret                           |\n| RedisPort      | Int    | A port for Redis                                           | 6379                             |\n| RedisServer    | String | FQDN for the Redis Server                                  | redis.cool.io                    |\n| RedisTLS       | Bool   | Attempt to talk to Redis using TLS                         | true, false                      |\n| RenotifyTime   | Int    | Number of seconds to not renotify a user if from same IP   | 7200                             |\n| SlackKey       | String | The key for Slack                                          | xozz-...                         |\n| TemplatePath   | String | A path to a go template file                               | /etc/vpnnotify.msg               |\n\n## The Template\nThis is where you actually put the message that will be sent via Slack. Here's a sample message template:\n```\nYou just started a VPN session into *{{.Env}}* from {{.IP}}{{if eq .GeoIP true}} ({{if ne .City \"\"}}{{.City}}, {{end}}{{if ne .State \"\"}}{{.State}},{{end}}{{if ne .Country \"\"}} {{.Country}}{{end}}){{end}}. If this wasn't you, please reach out to ops immediately.\n```\n\nThere aren't a lot of variables to use in the template. Here's what is available\nright now:\n\n| Variable | Type   | Purpose                                               |\n|----------|--------|-------------------------------------------------------|\n| Env      | String | The environment the person logged into (dev/prod/???) |\n| IP       | String | The connecting IP address                             |\n| GeoIP    | Bool   | If GeoIP is enabled                                   |\n| City     | String | GeoIP: City                                           |\n| Country  | String | GeoIP: Country                                        |\n| State    | String | GeoIP: State or other municipal division              |\n\n## Configuring OpenVPN\nSet `client-connect = /path/to/vpnnotify` in your OpenVPN config. That's it.\n\n# Testing\nYou can run `vpnnotify` locally. On the command line, run\n`VPNNOTIFY_CONFIG=./vpnnotify.json common_name=username untrusted_ip=8.8.8.8 ./vpnnotify`\nwhich will simulate `username` logging into the VPN from `8.8.8.8`.\n\nWhen you test, you may want to set `RenotifyTime` shorter, depending on\nwhat you're testing.\n\n# Contributing\nThere are a few things we know could use some improvement:\n\n* We currently infer environment based on the OpenVPN host's hostname - maybe\n  this should be done via the configuration file? (ideal for first time\n  contributors!)\n* We alert on the positive condition (known user successfully connects). What\n  about users who don't have the slackUser attribute? Are there other negative\n  conditions we're missing?\n* `vpnnotify` is fairly unsophisticated. Some statistical analysis would be\n  extremely helpful in dialing down \"alert fatigue\" with the caveat that any\n  statistical analysis should not require too much \"extra\" to run\n\n## How to Contribute\nBefore you start contributing to any project sponsored by F5, Inc. (F5) on GitHub, you will need to sign a Contributor License Agreement (CLA). This document can be provided to you once you submit a GitHub issue that you contemplate contributing code to, or after you issue a pull request.\n\nIf you are signing as an individual, we recommend that you talk to your employer (if applicable) before signing the CLA since some employment agreements may have restrictions on your contributions to other projects. Otherwise by submitting a CLA you represent that you are legally entitled to grant the licenses recited therein.\n\nIf your employer has rights to intellectual property that you create, such as your contributions, you represent that you have received permission to make contributions on behalf of that employer, that your employer has waived such rights for your contributions, or that your employer has executed a separate CLA with F5.\n\nIf you are signing on behalf of a company, you represent that you are legally entitled to grant the license recited therein. You represent further that each employee of the entity that submits contributions is authorized to submit such contributions on behalf of the entity pursuant to the CLA.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthreatstack%2Fvpnnotify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthreatstack%2Fvpnnotify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthreatstack%2Fvpnnotify/lists"}