mirror of
https://github.com/docker/docker-credential-helpers.git
synced 2026-06-28 07:11:36 +05:30
Compare commits
149 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 292722b503 | |||
| 979dcc4762 | |||
| f411a65c31 | |||
| 9629bd77ad | |||
| f642c26173 | |||
| 8fc330691f | |||
| 6a3e64c0b4 | |||
| 218f1787ac | |||
| 8396edb35f | |||
| a3d1ffcdb2 | |||
| c03d56cfdc | |||
| 7f484550bc | |||
| a90e3fa153 | |||
| ffb3232f6c | |||
| 1050848357 | |||
| 7d66ae02a6 | |||
| 13475b4387 | |||
| 91af1de9af | |||
| 4977273244 | |||
| a228f55121 | |||
| dbfb389f83 | |||
| bd83e02ad0 | |||
| a6e03c60ab | |||
| c842499594 | |||
| c1c7dd7011 | |||
| 83d38ea5e6 | |||
| 72391b37df | |||
| d0668939bb | |||
| a51d46e82c | |||
| ea29253d2a | |||
| 90bf5da1c9 | |||
| 6a2f53622b | |||
| 9f5511c8d5 | |||
| c740b990c2 | |||
| 372315b138 | |||
| c8c415f9f7 | |||
| a652f8e7e6 | |||
| 2860ca4b4d | |||
| 2103f1bcee | |||
| bdd92dd0d3 | |||
| 5944f8a485 | |||
| 0b9180511f | |||
| d440e5916e | |||
| 129017a3cd | |||
| 99079cafd2 | |||
| ae1d1ec013 | |||
| db0ac44c97 | |||
| 0dbcdb66a7 | |||
| c324fe0a6f | |||
| 19557f8fff | |||
| 94483d2d23 | |||
| 4c9fc240ed | |||
| f8e94d91c0 | |||
| 6f4e3abfc8 | |||
| ec695cee3c | |||
| fa89a70db3 | |||
| 14d46ffd7e | |||
| c20f883316 | |||
| 7a60d70114 | |||
| 814dbb3b5a | |||
| ed91395f20 | |||
| 5c5b09e7f8 | |||
| 8282d3336a | |||
| a7ff1c7d16 | |||
| 4a8c2d1d81 | |||
| 810dcd4ed5 | |||
| f09e79d741 | |||
| b21b69c8ee | |||
| 7f00c5c8bd | |||
| 1ed95cb020 | |||
| 88cb947f19 | |||
| 9ff5b6126b | |||
| 62d8c84526 | |||
| 2749e559d9 | |||
| 4ede49ca78 | |||
| a001d639ae | |||
| 9817a23b17 | |||
| 11e6d3772c | |||
| a649a36b27 | |||
| 9ddc7c7f86 | |||
| e6a96be547 | |||
| 069ceb8b56 | |||
| 7ce5629658 | |||
| 37c4a6b158 | |||
| da93839996 | |||
| da7f673a5e | |||
| d5c91a2f56 | |||
| e9656dd67f | |||
| 52f0cb6de6 | |||
| bde49b32dd | |||
| b3ec48855b | |||
| 0db06514b9 | |||
| d7835111fd | |||
| be409725f3 | |||
| 20454add32 | |||
| 5674caebaf | |||
| ac5992b5f4 | |||
| 205638eeb3 | |||
| 29c449aea8 | |||
| 2f246b8570 | |||
| 0ab1dd03b3 | |||
| 3526ac5303 | |||
| 7c04fb1f9c | |||
| d4880decb1 | |||
| 214ecc1238 | |||
| 056f8792b7 | |||
| da1d534b46 | |||
| a2d8aac9dd | |||
| 48ab0d84e8 | |||
| e50298d973 | |||
| 2c31fa46db | |||
| a37d38a864 | |||
| 51249117fa | |||
| 3289c31a02 | |||
| ebd9dc6c79 | |||
| cc29c66bcc | |||
| dd465ef1f3 | |||
| a9d6be0a41 | |||
| becf2f2a95 | |||
| 6dfcfc15aa | |||
| 5a61a5bc61 | |||
| f522992580 | |||
| 9ec715d8ed | |||
| 9582eb6661 | |||
| e847de4b42 | |||
| 5302241995 | |||
| e5695df009 | |||
| acd90ea9da | |||
| 018b71b3c3 | |||
| 662759718c | |||
| fd0197473f | |||
| ab7fd12c67 | |||
| 6f1a1a10c1 | |||
| 667e4702b8 | |||
| 8fee1128b6 | |||
| 70f476531f | |||
| ebbaee6ed4 | |||
| e7e9118856 | |||
| 0e5dbc62ad | |||
| a251a3e4c5 | |||
| 4f1c080d3b | |||
| 17020d97fe | |||
| 2fc2313bb1 | |||
| e595cd6946 | |||
| a4e88fa4cf | |||
| 48bfed47cc | |||
| 6e2a858a7a | |||
| 8086f00d7d | |||
| 16c3805fc7 |
@@ -0,0 +1 @@
|
||||
/bin
|
||||
@@ -0,0 +1,4 @@
|
||||
# Code of conduct
|
||||
|
||||
- [Moby community guidelines](https://github.com/moby/moby/blob/master/CONTRIBUTING.md#moby-community-guidelines)
|
||||
- [Docker Code of Conduct](https://github.com/docker/code-of-conduct)
|
||||
@@ -0,0 +1,289 @@
|
||||
# Contribute to this repository
|
||||
|
||||
This page contains information about reporting issues as well as some tips and
|
||||
guidelines useful to experienced open source contributors.
|
||||
|
||||
## Reporting security issues
|
||||
|
||||
The project maintainers take security seriously. If you discover a security
|
||||
issue, please bring it to their attention right away!
|
||||
|
||||
**Please _DO NOT_ file a public issue**, instead send your report privately to
|
||||
[security@docker.com](mailto:security@docker.com).
|
||||
|
||||
Security reports are greatly appreciated and we will publicly thank you for it.
|
||||
We also like to send gifts—if you're into schwag, make sure to let
|
||||
us know. We currently do not offer a paid security bounty program, but are not
|
||||
ruling it out in the future.
|
||||
|
||||
|
||||
## Reporting other issues
|
||||
|
||||
A great way to contribute to the project is to send a detailed report when you
|
||||
encounter an issue. We always appreciate a well-written, thorough bug report,
|
||||
and will thank you for it!
|
||||
|
||||
Check that [the issue database](https://github.com/docker/docker-credential-helpers/issues)
|
||||
doesn't already include that problem or suggestion before submitting an issue.
|
||||
If you find a match, you can use the "subscribe" button to get notified on
|
||||
updates. Do *not* leave random "+1" or "I have this too" comments, as they
|
||||
only clutter the discussion, and don't help resolving it. However, if you
|
||||
have ways to reproduce the issue or have additional information that may help
|
||||
resolving the issue, please leave a comment.
|
||||
|
||||
Include the steps required to reproduce the problem if possible and applicable.
|
||||
This information will help us review and fix your issue faster. When sending
|
||||
lengthy log-files, consider posting them as an attachment, instead of posting
|
||||
inline.
|
||||
|
||||
**Do not forget to remove sensitive data from your logfiles before submitting**
|
||||
(you can replace those parts with "REDACTED").
|
||||
|
||||
### Pull requests are always welcome
|
||||
|
||||
Not sure if that typo is worth a pull request? Found a bug and know how to fix
|
||||
it? Do it! We will appreciate it.
|
||||
|
||||
If your pull request is not accepted on the first try, don't be discouraged! If
|
||||
there's a problem with the implementation, hopefully you received feedback on
|
||||
what to improve.
|
||||
|
||||
We're trying very hard to keep this project lean and focused. We don't want it to
|
||||
do everything for everybody. This means that we might decide against
|
||||
incorporating a new feature. However, there might be a way to implement that
|
||||
feature *on top of* code in this project.
|
||||
|
||||
### Design and cleanup proposals
|
||||
|
||||
You can propose new designs for existing features. You can also design
|
||||
entirely new features. We really appreciate contributors who want to refactor or
|
||||
otherwise cleanup our project.
|
||||
|
||||
### Sign your work
|
||||
|
||||
The sign-off is a simple line at the end of the explanation for the patch. Your
|
||||
signature certifies that you wrote the patch or otherwise have the right to pass
|
||||
it on as an open-source patch. The rules are pretty simple: if you can certify
|
||||
the below (from [developercertificate.org](https://developercertificate.org)):
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
Then you just add a line to every git commit message:
|
||||
|
||||
Signed-off-by: Joe Smith <joe.smith@email.com>
|
||||
|
||||
**Use your real name** (sorry, no pseudonyms or anonymous contributions.)
|
||||
|
||||
If you set your `user.name` and `user.email` git configs, you can sign your
|
||||
commit automatically with `git commit -s`.
|
||||
|
||||
### Run the unit- and integration-tests
|
||||
|
||||
To validate PRs before submitting them you should run:
|
||||
|
||||
```bash
|
||||
$ make validate
|
||||
```
|
||||
|
||||
To run the tests:
|
||||
|
||||
```bash
|
||||
$ make test
|
||||
```
|
||||
|
||||
To generate new vendored files with go modules run:
|
||||
|
||||
```bash
|
||||
$ make vendor
|
||||
```
|
||||
|
||||
|
||||
### Conventions
|
||||
|
||||
- Fork the repository and make changes on your fork in a feature branch
|
||||
- Submit tests for your changes. See [run the unit- and integration-tests](#run-the-unit--and-integration-tests)
|
||||
for details.
|
||||
- [Sign your work](#sign-your-work)
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
|
||||
committing your changes. Most editors have plug-ins that do this automatically.
|
||||
|
||||
Pull request descriptions should be as clear as possible and include a
|
||||
reference to all the issues that they address. Be sure that the [commit
|
||||
messages](#commit-messages) also contain the relevant information.
|
||||
|
||||
### Successful Changes
|
||||
|
||||
Before contributing large or high impact changes, make the effort to coordinate
|
||||
with the maintainers of the project before submitting a pull request. This
|
||||
prevents you from doing extra work that may or may not be merged.
|
||||
|
||||
Large PRs that are just submitted without any prior communication are unlikely
|
||||
to be successful.
|
||||
|
||||
While pull requests are the methodology for submitting changes to code, changes
|
||||
are much more likely to be accepted if they are accompanied by additional
|
||||
engineering work. While we don't define this explicitly, most of these goals
|
||||
are accomplished through communication of the design goals and subsequent
|
||||
solutions. Often times, it helps to first state the problem before presenting
|
||||
solutions.
|
||||
|
||||
Typically, the best methods of accomplishing this are to submit an issue,
|
||||
stating the problem. This issue can include a problem statement and a
|
||||
checklist with requirements. If solutions are proposed, alternatives should be
|
||||
listed and eliminated. Even if the criteria for elimination of a solution is
|
||||
frivolous, say so.
|
||||
|
||||
Larger changes typically work best with design documents. These are focused on
|
||||
providing context to the design at the time the feature was conceived and can
|
||||
inform future documentation contributions.
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Commit messages must start with a capitalized and short summary (max. 50 chars)
|
||||
written in the imperative, followed by an optional, more detailed explanatory
|
||||
text which is separated from the summary by an empty line.
|
||||
|
||||
Commit messages should follow best practices, including explaining the context
|
||||
of the problem and how it was solved, including in caveats or follow up changes
|
||||
required. They should tell the story of the change and provide readers
|
||||
understanding of what led to it.
|
||||
|
||||
If you're lost about what this even means, please see [How to Write a Git
|
||||
Commit Message](http://chris.beams.io/posts/git-commit/) for a start.
|
||||
|
||||
In practice, the best approach to maintaining a nice commit message is to
|
||||
leverage a `git add -p` and `git commit --amend` to formulate a solid
|
||||
changeset. This allows one to piece together a change, as information becomes
|
||||
available.
|
||||
|
||||
If you squash a series of commits, don't just submit that. Re-write the commit
|
||||
message, as if the series of commits was a single stroke of brilliance.
|
||||
|
||||
That said, there is no requirement to have a single commit for a PR, as long as
|
||||
each commit tells the story. For example, if there is a feature that requires a
|
||||
package, it might make sense to have the package in a separate commit then have
|
||||
a subsequent commit that uses it.
|
||||
|
||||
Remember, you're telling part of the story with the commit message. Don't make
|
||||
your chapter weird.
|
||||
|
||||
### Review
|
||||
|
||||
Code review comments may be added to your pull request. Discuss, then make the
|
||||
suggested modifications and push additional commits to your feature branch. Post
|
||||
a comment after pushing. New commits show up in the pull request automatically,
|
||||
but the reviewers are notified only when you comment.
|
||||
|
||||
Pull requests must be cleanly rebased on top of master without multiple branches
|
||||
mixed into the PR.
|
||||
|
||||
> **Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
|
||||
> feature branch to update your pull request rather than `merge master`.
|
||||
|
||||
Before you make a pull request, squash your commits into logical units of work
|
||||
using `git rebase -i` and `git push -f`. A logical unit of work is a consistent
|
||||
set of patches that should be reviewed together: for example, upgrading the
|
||||
version of a vendored dependency and taking advantage of its now available new
|
||||
feature constitute two separate units of work. Implementing a new function and
|
||||
calling it in another file constitute a single logical unit of work. The very
|
||||
high majority of submissions should have a single commit, so if in doubt: squash
|
||||
down to one.
|
||||
|
||||
- After every commit, [make sure the test suite passes](#run-the-unit--and-integration-tests).
|
||||
Include documentation changes in the same pull request so that a revert would
|
||||
remove all traces of the feature or fix.
|
||||
- Include an issue reference like `closes #XXXX` or `fixes #XXXX` in the PR
|
||||
description that close an issue. Including references automatically closes
|
||||
the issue on a merge.
|
||||
- Do not add yourself to the `AUTHORS` file, as it is regenerated regularly
|
||||
from the Git history.
|
||||
- See the [Coding Style](#coding-style) for further guidelines.
|
||||
|
||||
|
||||
### Merge approval
|
||||
|
||||
Project maintainers use LGTM (Looks Good To Me) in comments on the code review to
|
||||
indicate acceptance, or use the Github review approval feature.
|
||||
|
||||
|
||||
## Coding Style
|
||||
|
||||
Unless explicitly stated, we follow all coding guidelines from the Go
|
||||
community. While some of these standards may seem arbitrary, they somehow seem
|
||||
to result in a solid, consistent codebase.
|
||||
|
||||
It is possible that the code base does not currently comply with these
|
||||
guidelines. We are not looking for a massive PR that fixes this, since that
|
||||
goes against the spirit of the guidelines. All new contributions should make a
|
||||
best effort to clean up and make the code base better than they left it.
|
||||
Obviously, apply your best judgement. Remember, the goal here is to make the
|
||||
code base easier for humans to navigate and understand. Always keep that in
|
||||
mind when nudging others to comply.
|
||||
|
||||
The rules:
|
||||
|
||||
1. All code should be formatted with `gofmt -s`.
|
||||
2. All code should pass the default levels of
|
||||
[`golint`](https://github.com/golang/lint).
|
||||
3. All code should follow the guidelines covered in [Effective Go](http://golang.org/doc/effective_go.html)
|
||||
and [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||
4. Comment the code. Tell us the why, the history and the context.
|
||||
5. Document _all_ declarations and methods, even private ones. Declare
|
||||
expectations, caveats and anything else that may be important. If a type
|
||||
gets exported, having the comments already there will ensure it's ready.
|
||||
6. Variable name length should be proportional to its context and no longer.
|
||||
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
|
||||
In practice, short methods will have short variable names and globals will
|
||||
have longer names.
|
||||
7. No underscores in package names. If you need a compound name, step back,
|
||||
and re-examine why you need a compound name. If you still think you need a
|
||||
compound name, lose the underscore.
|
||||
8. No utils or helpers packages. If a function is not general enough to
|
||||
warrant its own package, it has not been written generally enough to be a
|
||||
part of a util package. Just leave it unexported and well-documented.
|
||||
9. All tests should run with `go test` and outside tooling should not be
|
||||
required. No, we don't need another unit testing framework. Assertion
|
||||
packages are acceptable if they provide _real_ incremental value.
|
||||
10. Even though we call these "rules" above, they are actually just
|
||||
guidelines. Since you've read all the rules, you now know that.
|
||||
|
||||
If you are having trouble getting into the mood of idiomatic Go, we recommend
|
||||
reading through [Effective Go](https://golang.org/doc/effective_go.html). The
|
||||
[Go Blog](https://blog.golang.org) is also a great resource.
|
||||
@@ -0,0 +1,12 @@
|
||||
# Reporting security issues
|
||||
|
||||
The project maintainers take security seriously. If you discover a security
|
||||
issue, please bring it to their attention right away!
|
||||
|
||||
**Please _DO NOT_ file a public issue**, instead send your report privately to
|
||||
[security@docker.com](mailto:security@docker.com).
|
||||
|
||||
Security reports are greatly appreciated, and we will publicly thank you for it.
|
||||
We also like to send gifts—if you're into schwag, make sure to let
|
||||
us know. We currently do not offer a paid security bounty program, but are not
|
||||
ruling it out in the future.
|
||||
@@ -0,0 +1,194 @@
|
||||
name: build
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
DESTDIR: ./bin
|
||||
GO_VERSION: 1.21.6
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- lint
|
||||
- validate-vendor
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Run
|
||||
run: |
|
||||
make ${{ matrix.target }}
|
||||
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-22.04
|
||||
- ubuntu-20.04
|
||||
- macOS-11
|
||||
- windows-2022
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
-
|
||||
name: Install deps (ubuntu)
|
||||
if: startsWith(matrix.os, 'ubuntu-')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dbus-x11 gnome-keyring libsecret-1-dev pass
|
||||
-
|
||||
name: Install deps (macOS)
|
||||
if: startsWith(matrix.os, 'macOS-')
|
||||
run: |
|
||||
brew install pass
|
||||
-
|
||||
name: GPG conf
|
||||
if: ${{ !startsWith(matrix.os, 'windows-') }}
|
||||
uses: actions/github-script@v6
|
||||
id: gpg
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const gnupgfolder = `${require('os').homedir()}/.gnupg`;
|
||||
if (!fs.existsSync(gnupgfolder)){
|
||||
fs.mkdirSync(gnupgfolder);
|
||||
}
|
||||
fs.copyFile('.github/workflows/fixtures/gpg.conf', `${gnupgfolder}/gpg.conf`, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
core.setOutput('key', fs.readFileSync('.github/workflows/fixtures/7D851EB72D73BDA0.key', {encoding: 'utf8'}));
|
||||
core.setOutput('passphrase', fs.readFileSync('.github/workflows/fixtures/7D851EB72D73BDA0.pass', {encoding: 'utf8'}));
|
||||
-
|
||||
name: Import GPG key
|
||||
if: ${{ !startsWith(matrix.os, 'windows-') }}
|
||||
uses: crazy-max/ghaction-import-gpg@v5
|
||||
with:
|
||||
gpg_private_key: ${{ steps.gpg.outputs.key }}
|
||||
passphrase: ${{ steps.gpg.outputs.passphrase }}
|
||||
trust_level: 5
|
||||
-
|
||||
name: Init pass
|
||||
if: ${{ !startsWith(matrix.os, 'windows-') }}
|
||||
run: |
|
||||
pass init 7D851EB72D73BDA0
|
||||
shell: bash
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
make test COVERAGEDIR=${{ env.DESTDIR }}
|
||||
shell: bash
|
||||
-
|
||||
name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ${{ env.DESTDIR }}/coverage.txt
|
||||
|
||||
test-sandboxed:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@v2
|
||||
with:
|
||||
targets: test
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=test
|
||||
*.cache-to=type=gha,scope=test,mode=max
|
||||
-
|
||||
name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ${{ env.DESTDIR }}//coverage.txt
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
-
|
||||
name: Build
|
||||
run: |
|
||||
make release
|
||||
env:
|
||||
CACHE_FROM: type=gha,scope=build
|
||||
CACHE_TO: type=gha,scope=build,mode=max
|
||||
-
|
||||
name: List artifacts
|
||||
run: |
|
||||
tree -nh ${{ env.DESTDIR }}
|
||||
-
|
||||
name: Check artifacts
|
||||
run: |
|
||||
find ${{ env.DESTDIR }} -type f -exec file -e ascii -e text -- {} +
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-credential-helpers
|
||||
path: ${{ env.DESTDIR }}/*
|
||||
if-no-files-found: error
|
||||
-
|
||||
name: GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
draft: true
|
||||
files: ${{ env.DESTDIR }}/*
|
||||
|
||||
build-deb:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Build
|
||||
run: |
|
||||
make deb
|
||||
@@ -0,0 +1,106 @@
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
lQdGBF6tzaABEACjFbX7PFEG6vDPN2MPyxYW7/3o/sonORj4HXUFjFxxJxktJ3x3
|
||||
N1ayHPJ1lqIeoiY7jVbq0ZdEVGkd3YsKG9ZMdZkzGzY6PQPC/+M8OnzOiOPwUdWc
|
||||
+Tdhh115LvVz0MMKYiab6Sn9cgxj9On3LCQKpjvMDpPo9Ttf6v2GQIw8h2ACvdzQ
|
||||
71LtIELS/I+dLbfZiwpUu2fhQT13EJkEnYMOYwM5jNUd66P9itUc7MrOWjkicrKP
|
||||
oF1dQaCM+tuKuxvD8WLdiwU5x60NoGkJHHUehKQXl2dVzjpqEqHKEBJt9tfJ9lpE
|
||||
YIisgwB8o3pes0fgCehjW2zI95/o9+ayJ6nl4g5+mSvWRXEu66h71nwM0Yuvquk8
|
||||
3me7qhYfDrDdCwcxS5BS1hwakTgUQLD99FZjbx1j8sq96I65O0GRdyU2PR8KIjwu
|
||||
JrkTH4ZlKxK3FQghUhFoA5GkiDb+eClmRMSni5qg+81T4XChmUkEprA3eWCHL+Ma
|
||||
xRNNxLS+r6hH9HG5JBxpV3iaTI9HHpnQKhEeaLXqsUTDZliN9hP7Ywo8bpUB8j2d
|
||||
oWYwDV4dPyMKr6Fb8RDCh2q5gJGbVp8w/NmmBTeL+IP2fFggJkRfyumv3Ul7x66L
|
||||
tBFQ4rYo4JUUrGweSTneG6REIgxH66hIrNl6Vo/D1ZyknTe1dMOu/BTkkQARAQAB
|
||||
/gcDAqra8KO+h3bfyu90vxTL1ro4x/x9il7VBcWlIR4cBP7Imgxv+T4hwPIu8P1x
|
||||
lOlxLNWegFOV0idoTy1o3VLLBev/F+IlspX4A+2XEIddR6nZnKFi0Lv2L4TKgE9E
|
||||
VJJTszmviDIRLMLN9dWzDfA8hj5tR5Inot92CHRF414AS22JHvlhbFSLQnjqsN+C
|
||||
n1cQpNOJhkxsSfZsxjnFa/70y/u8v0o8mzyLZmk9HpzRHGzoz8IfpLp8OTqBR9u6
|
||||
zzoKLy16zZO55OKbj7h8uVZvDUq9l8iDICpqWMdZqBJIl56MBexYKgYxh3YO/8v2
|
||||
oXli+8Xuaq5QLiCN3yT7IbKoYzplnFfaJwFiMh7R1iPLXaYAZ0qdRijlbtseTK1m
|
||||
oHNkwUbxVzjkh4LfE8UpmMwZn5ZjWni3230SoiXuKy0OHkGvwGvWWAL1mEuoYuUI
|
||||
mFMcH5MnixP8oQYZKDj2IR/yEeOpdU6B/tr3Tk1NidLf7pUMqG7Ff1NU6dAUeBpa
|
||||
9xahITMjHvrhgMISY4IYZep5cEnVw8lQTpUJtW/ePMzrFhu3sA7oNdj9joW/VMfz
|
||||
H7MHwwavtICsYqoqV3lnjX4EC9dW6o8PTUg2u956dmtK7KAyUK/+w2aLNGT28ChN
|
||||
jhRYHvHzB9Kw5asqI/lTM49eqslBqYQMTTjdBphkYuSZQzNMf291j/ZmoLhD1A1a
|
||||
S8tUnNygKV4D1cJYgSXfzhFoU8ib/0SPo+KqQ+CzGS+wxXg6WNBA6wepTjpnVVx3
|
||||
4JADP8IJcDC3P0iwAreWjSy15F1cvemFFB0SLNUkyZGzsxtKzbM1+8khl68+eazC
|
||||
LzRj0rxfIF5znWjX1QFhKxCk6eF0IWDY0+b3DBkmChME9YDXJ3TthcqA7JgcX4JI
|
||||
M4/wdqhgerJYOmj+i2Q0M+Bu02icOJYMwTMMsDVl7XGHkaCuRgZ54eZAUH7JFwUm
|
||||
1Ct3tcaqiTMmz0ngHVqBTauzgqKDvzwdVqdfg05H364nJMay/3omR6GayIb5CwSo
|
||||
xdNVwG3myPPradT9MP09mDr4ys2zcnQmCkvTVBF6cMZ1Eh6PQQ8CyQWv0zkaBnqj
|
||||
JrM1hRpgW4ZlRosSIjCaaJjolN5QDcXBM9TbW9ww+ZYstazN2bV1ZQ7BEjlHQPa1
|
||||
BhzMsvqkbETHsIpDNF52gZKn3Q9eIX05BeadzpHUb5/XOheIHVIdhSaTlgl/qQW5
|
||||
hQgPGSzSV6KhXEY7aevTdvOgq++WiELkjfz2f2lQFesTjFoQWEvxVDUmLxHtEhaN
|
||||
DOuh4H3mX5Opn3pLQmqWVhJTbFdx+g5qQd0NCW4mDaTFWTRLFLZQsSJxDSeg9xrY
|
||||
gmaii8NhMZRwquADW+6iU6KfraBhngi7HRz4TfqPr9ma/KUY464cqim1fnwXejyx
|
||||
jsb5YHR9R66i+F6P/ysF5w+QuVdDt1fnf9GLay0r6qxpA8ft2vGPcDs4806Huj+7
|
||||
Aq5VeJaNkCuh3GR3xVnCFAz/7AtkO6xKuZm8B3q904UuMdSmkhWbaobIuF/B2B6S
|
||||
eawIXQHEOplK3ic26d8Ckf4gbjeORfELcMAEi5nGXpTThCdmxQApCLxAYYnTfQT1
|
||||
xhlDwT9xPEabo98mIwJJsAU5VsTDYW+qfo4qIx8gYoSKc9Xu3yVh3n+9k43Gcm5V
|
||||
9lvK1slijf+TzODZt/jsmkF8mPjXyP5KOI+xQp/m4PxW3pp57YrYj/Rnwga+8DKX
|
||||
jMsW7mLAAZ/e+PY6z/s3x1Krfk+Bb5Ph4mI0zjw5weQdtyEToRgveda0GEpvZSBU
|
||||
ZXN0ZXIgPGpvZUBmb28uYmFyPokCNgQQAQgAIAUCXq3NoAYLCQcIAwIEFQgKAgQW
|
||||
AgEAAhkBAhsDAh4BAAoJEH2FHrctc72gxtQP/AulaClIcn/kDt43mhYnyLglPfbo
|
||||
AqPlU26chXolBg0Wo0frFY3aIs5SrcWEf8aR4XLwCFGyi3vya0CUxjghN5tZBYqo
|
||||
vswbT00zP3ohxxlJFCRRR9bc7OZXCgTddtfVf6EKrUAzIkbWyAhaJnwJy/1UGpSw
|
||||
SEO/KpastrVKf3sv1wqOeFQ4DFyjaNda+xv3dVWS8db7KogqJiPFZXrQK3FKVIxS
|
||||
fxRSmKaYN7//d+xwVAEY++RrnL/o8B2kV6N68cCpQWJELyYnJzis9LBcWd/3wiYh
|
||||
efTyY+ePKUjcB+kEZnyJfLc7C2hll2e7UJ0fxv+k8vHReRhrNWmGRXsjNRxiw3U0
|
||||
hfvxD/C8nyqAbeTHp4XDX78Tc3XCysAqIYboIL+RyewDMjjLj5vzUYAdUdtyNaD7
|
||||
C6M2R6pN1GAt52CJmC/Z6F7W7GFGoYOdEkVdMQDsjCwScyEUNlGj9Zagw5M2EgSe
|
||||
6gaHgMgTzsMzCc4W6WV5RcS55cfDNOXtxPsMJTt4FmXrjl11prBzpMfpU5a9zxDZ
|
||||
oi54ZZ8VPE6jsT4Lzw3sni3c83wm28ArM20AzZ1vh7fk3Sfd0u4Yaz7s9JlEm5+D
|
||||
34tEyli28+QjCQc18EfQUiJqiYEJRxJXJ3esvMHfYi45pV/Eh5DgRW1305fUJV/6
|
||||
+rGpg0NejsHoZdZPnQdGBF6tzaABEAC4mVXTkVk6Kdfa4r5zlzsoIrR27laUlMkb
|
||||
OBMt+aokqS+BEbmTnMg6xIAmcUT5uvGAc8S/WhrPoYfc15fTUyHIz8ZbDoAg0LO6
|
||||
0Io4VkAvNJNEnsSV9VdLBh/XYlc4K49JqKyWTL4/FJFAGbsmHY3b+QU90AS6FYRv
|
||||
KeBAoiyebrjx0vmzb8E8h3xthVLN+AfMlR1ickY62zvnpkbncSMY/skur1D2KfbF
|
||||
3sFprty2pEtjFcyB5+18l2IyyHGOlEUw1PZdOAV4/Myh1EZRgYBPs80lYTJALCVF
|
||||
IdOakH33WJCImtNZB0AbDTABG+JtMjQGscOa0qzf1Y/7tlhgCrynBBdaIJTx95TD
|
||||
21BUHcHOu5yTIS6Ulysxfkv611+BiOKHgdq7DVGP78VuzA7bCjlP1+vHqIt3cnIa
|
||||
t2tEyuZ/XF4uc3/i4g0uP9r7AmtET7Z6SKECWjpVv+UEgLx5Cv+ql+LSKYQMvU9a
|
||||
i3B1F9fatn3FSLVYrL4aRxu4TSw9POb0/lgDNmN3lGQOsjGCZPibkHjgPEVxKuiq
|
||||
9Oi38/VTQ0ZKAmHwBTq1WTZIrPrCW0/YMQ6yIJZulwQ9Yx1cgzYzEfg04fPXlXMi
|
||||
vkvNpKbYIICzqj0/DVztz9wgpW6mnd0A2VX2dqbMM0fJUCHA6pj8AvXY4R+9Q4rj
|
||||
eWRK9ycInQARAQAB/gcDApjt7biRO0PEyrrAiUwDMsJL4/CVMu11qUWEPjKe2Grh
|
||||
ZTW3N+m3neKPRULu+LUtndUcEdVWUCoDzAJ7MwihZtV5vKST/5Scd2inonOaJqoA
|
||||
nS3wnEMN/Sc93HAZiZnFx3NKjQVNCwbuEs45mXkkcjLm2iadrTL8fL4acsu5IsvD
|
||||
LbDwVOPeNnHKl6Hr20e39fK0FuJEyH49JM6U3B1/8385sJB8+E24+hvSF81aMddh
|
||||
Ne4Bc3ZYiYaKxe1quPNKC0CQhAZiT7LsMfkInXr0hY1I+kISNXEJ1dPYOEWiv0Ze
|
||||
jD5Pupn34okKNEeBCx+dK8BmUCi6Jgs7McUA7hN0D/YUS++5fuR55UQq2j8Ui0tS
|
||||
P8GDr86upH3PgEL0STh9fYfJ7TesxurwonWjlmmT62Myl4Pr+RmpS6PXOnhtcADm
|
||||
eGLpzhTveFj4JBLMpyYHgBTqcs12zfprATOpsI/89kmQoGCZpG6+AbfSHqNNPdy2
|
||||
eqUCBhOZlIIda1z/cexmU3f/gBqyflFf8fkvmlO4AvI8aMH3OpgHdWnzh+AB51xj
|
||||
kmdD/oWel9v7Dz4HoZUfwFaLZ0fE3P9voD8e+sCwqQwVqRY4L/BOYPD5noVOKgOj
|
||||
ABNKu5uKrobj6rFUi6DTUCjFGcmoF1Sc06xFNaagUNggRbmlC/dz22RWdDUYv5ra
|
||||
N6TxIDkGC0cK6ujyK0nes3DN0aHjgwWuMXDYkN3UckiebI4Cv/eF9jvUKOSiIcy1
|
||||
RtxdazZS4dYg2LBMeJKVkPi5elsNyw2812nEY3du/nEkQYXfYgWOF27OR+g4Y9Yw
|
||||
1BiqJ1TTjbQnd/khOCrrbzDH1mw00+1XVsT6wjObuYqqxPPS87UrqmMf6OdoYfPm
|
||||
zEOnNLBnsJ5VQM3A3pcT40RfdBrZRO8LjGhzKTreyq3C+jz0RLa5HNE8GgOhGyck
|
||||
ME4h+RhXlE8KGM+tTo6PA1NJSrEt+8kZzxjP4rIEn0aVthCkNXK12inuXtnHm0ao
|
||||
iLUlQOsfPFEnzl0TUPd7+z7j/wB+XiKU/AyEUuB0mvdxdKtqXvajahOyhLjzHQhz
|
||||
ZnNlgANGtiqcSoJmkJ8yAvhrtQX51fQLftxbArRW1RYk/5l+Gy3azR+gUC17M6JN
|
||||
jrUYxn0zlAxDGFH7gACHUONwVekcuEffHzgu2lk7MyO1Y+lPnwabqjG0eWWHuU00
|
||||
hskJlXyhj7DeR12bwjYkyyjG62GvOH02g3OMvUgNGH+K321Dz539csCh/xwtg7Wt
|
||||
U3YAphU7htQ1dPDfk1IRs7DQo2L+ZTE57vmL5m0l6fTataEWBPUXkygfQFUJOM6Q
|
||||
yY76UEZww1OSDujNeY171NSTzXCVkUeAdAMXgjaHXWLK2QUQUoXbYX/Kr7Vvt9Fu
|
||||
Jh6eGjjp7dSjQ9+DW8CAB8vxd93gsQQGWYjmGu8khkEmx6OdZhmSbDbe915LQTb9
|
||||
sPhk2s5/Szsvr5W2JJ2321JI6KXBJMZvPC5jEBWmRzOYkRd2vloft+CSMfXF+Zfd
|
||||
nYtc6R3dvb9vcjo+a9wFtfcoDsO0MaPSM+9GB25MamdatmGX6iLOy9Re1UABwUi/
|
||||
VhTWNkP5uzqx0sDwHEIa2rYOwxpIZDwwjM3oOASCW1DDBQ0BI9KNjfIeL3ubx2mS
|
||||
2x8hFU9qSK4umoDNbzOqGPSlkdbiPcNjF2ZcSN1qQZiYdwLL5dw6APNyBVjxTN1J
|
||||
gkCdJ/HwAY+r93Lbl5g8gz8d0vJEyfn//34sn9u+toSTw55GcG9Ks1kSKIeDNh0h
|
||||
MiPm3HmJAh8EGAEIAAkFAl6tzaACGwwACgkQfYUety1zvaBV9hAAgliX36pXJ59g
|
||||
3I9/4R68e/fGg0FMM6D+01yCeiKApOYRrJ0cYKn7ITDYmHhlGGpBAie90UsqX12h
|
||||
hdLP7LoQx7sjTyzQt6JmpA8krIwi2ON7FKBkdYb8IYx4mE/5vKnYT4/SFnwTmnZY
|
||||
+m+NzK2U/qmhq8JyO8gozdAKJUcgz49IVv2Ij0tQ4qaPbyPwQxIDyKnT758nJhB1
|
||||
jTqo+oWtER8q3okzIlqcArqn5rDaNJx+DRYL4E/IddyHQAiUWUka8usIUqeW5reu
|
||||
zoPUE2CCfOJSGArkqHQQqMx0WEzjQTwAPaHrQbera4SbiV/o4CLCV/u5p1Qnig+Q
|
||||
iUsakmlD299t//125LIQEa5qzd9hRC7u1uJS7VdW8eGIEcZ0/XT/sr+z23z0kpZH
|
||||
D3dXPX0BwM4IP9xu31CNg10x0rKwjbxy8VaskFEelpqpu+gpAnxqMd1evpeUHcOd
|
||||
r5RgPgkNFfba9Nbxf7uEX+HOmsOM+kdtSmdGIvsBZjVnW31nnoDMp49jG4OynjrH
|
||||
cRuoM9sxdr6UDqb22CZ3/e0YN4UaZM3YDWMVaP/QBVgvIFcdByqNWezpd9T4ZUII
|
||||
MZlaV1uRnHg6B/zTzhIdMM80AXz6Uv6kw4S+Lt7HlbrnMT7uKLuvzH7cle0hcIUa
|
||||
PejgXO0uIRolYQ3sz2tMGhx1MfBqH64=
|
||||
=WbwB
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
@@ -0,0 +1 @@
|
||||
with stupid passphrase
|
||||
@@ -1,9 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
set -ex
|
||||
|
||||
sh -e /etc/init.d/xvfb start
|
||||
sleep 3 # give xvfb some time to start
|
||||
|
||||
# init key for pass
|
||||
gpg --batch --gen-key <<-EOF
|
||||
%echo Generating a standard key
|
||||
Key-Type: DSA
|
||||
@@ -17,6 +14,3 @@ Expire-Date: 0
|
||||
%commit
|
||||
%echo done
|
||||
EOF
|
||||
|
||||
key=$(gpg --no-auto-check-trustdb --list-secret-keys | grep ^sec | cut -d/ -f2 | cut -d" " -f1)
|
||||
pass init $key
|
||||
@@ -0,0 +1,3 @@
|
||||
default-cache-ttl 21600
|
||||
max-cache-ttl 31536000
|
||||
allow-preset-passphrase
|
||||
@@ -0,0 +1,71 @@
|
||||
################################################################################
|
||||
# GnuPG Options
|
||||
|
||||
# (OpenPGP-Configuration-Options)
|
||||
# Assume that command line arguments are given as UTF8 strings.
|
||||
utf8-strings
|
||||
|
||||
# (OpenPGP-Protocol-Options)
|
||||
# Set the list of personal digest/cipher/compression preferences. This allows
|
||||
# the user to safely override the algorithm chosen by the recipient key
|
||||
# preferences, as GPG will only select an algorithm that is usable by all
|
||||
# recipients.
|
||||
personal-digest-preferences SHA512 SHA384 SHA256 SHA224
|
||||
personal-cipher-preferences AES256 AES192 AES CAST5 CAMELLIA192 BLOWFISH TWOFISH CAMELLIA128 3DES
|
||||
personal-compress-preferences ZLIB BZIP2 ZIP
|
||||
|
||||
# Set the list of default preferences to string. This preference list is used
|
||||
# for new keys and becomes the default for "setpref" in the edit menu.
|
||||
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
|
||||
|
||||
# (OpenPGP-Esoteric-Options)
|
||||
# Use name as the message digest algorithm used when signing a key. Running the
|
||||
# program with the command --version yields a list of supported algorithms. Be
|
||||
# aware that if you choose an algorithm that GnuPG supports but other OpenPGP
|
||||
# implementations do not, then some users will not be able to use the key
|
||||
# signatures you make, or quite possibly your entire key.
|
||||
#
|
||||
# SHA-1 is the only algorithm specified for OpenPGP V4. By changing the
|
||||
# cert-digest-algo, the OpenPGP V4 specification is not met but with even
|
||||
# GnuPG 1.4.10 (release 2009) supporting SHA-2 algorithm, this should be safe.
|
||||
# Source: https://tools.ietf.org/html/rfc4880#section-12.2
|
||||
cert-digest-algo SHA512
|
||||
digest-algo SHA256
|
||||
|
||||
# Selects how passphrases for symmetric encryption are mangled. 3 (the default)
|
||||
# iterates the whole process a number of times (see --s2k-count).
|
||||
s2k-mode 3
|
||||
|
||||
# (OpenPGP-Protocol-Options)
|
||||
# Use name as the cipher algorithm for symmetric encryption with a passphrase
|
||||
# if --personal-cipher-preferences and --cipher-algo are not given. The
|
||||
# default is AES-128.
|
||||
s2k-cipher-algo AES256
|
||||
|
||||
# (OpenPGP-Protocol-Options)
|
||||
# Use name as the digest algorithm used to mangle the passphrases for symmetric
|
||||
# encryption. The default is SHA-1.
|
||||
s2k-digest-algo SHA512
|
||||
|
||||
# (OpenPGP-Protocol-Options)
|
||||
# Specify how many times the passphrases mangling for symmetric encryption is
|
||||
# repeated. This value may range between 1024 and 65011712 inclusive. The
|
||||
# default is inquired from gpg-agent. Note that not all values in the
|
||||
# 1024-65011712 range are legal and if an illegal value is selected, GnuPG will
|
||||
# round up to the nearest legal value. This option is only meaningful if
|
||||
# --s2k-mode is set to the default of 3.
|
||||
s2k-count 1015808
|
||||
|
||||
################################################################################
|
||||
# GnuPG View Options
|
||||
|
||||
# Select how to display key IDs. "long" is the more accurate (but less
|
||||
# convenient) 16-character key ID. Add an "0x" to include an "0x" at the
|
||||
# beginning of the key ID.
|
||||
keyid-format 0xlong
|
||||
|
||||
# List all keys with their fingerprints. This is the same output as --list-keys
|
||||
# but with the additional output of a line with the fingerprint. If this
|
||||
# command is given twice, the fingerprints of all secondary keys are listed too.
|
||||
with-fingerprint
|
||||
with-fingerprint
|
||||
+1
-2
@@ -1,2 +1 @@
|
||||
bin
|
||||
release
|
||||
/bin
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
run:
|
||||
timeout: 10m
|
||||
modules-download-mode: vendor
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gofmt
|
||||
- govet
|
||||
- depguard
|
||||
- goimports
|
||||
- ineffassign
|
||||
- misspell
|
||||
- unused
|
||||
- revive
|
||||
- staticcheck
|
||||
- typecheck
|
||||
disable-all: true
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
deny:
|
||||
- pkg: "io/ioutil"
|
||||
desc: The io/ioutil package has been deprecated. See https://go.dev/doc/go1.16#ioutil
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- revive
|
||||
text: "stutters"
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
---
|
||||
# See appveyor.yml for windows build.
|
||||
sudo: required
|
||||
language: go
|
||||
dist: trusty
|
||||
osx_image: xcode10.1
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
notifications:
|
||||
email: false
|
||||
go:
|
||||
- 1.12.x
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libsecret-1-dev
|
||||
- pass
|
||||
before_script:
|
||||
- make deps
|
||||
- "export DISPLAY=:99.0"
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sh ci/before_script_linux.sh; fi
|
||||
- make validate
|
||||
script: make test
|
||||
@@ -1,61 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
This changelog tracks the releases of docker-credential-helpers.
|
||||
This project includes different binaries per platform.
|
||||
The platform released is identified after the tag name.
|
||||
|
||||
## v0.6.0 (Go client, Linux)
|
||||
|
||||
- New credential helper on Linux using `pass`
|
||||
- New entry point for passing environment variables when calling a credential helper
|
||||
- Add a Makefile rule generating a Windows release binary
|
||||
|
||||
### Note
|
||||
|
||||
`pass` needs to be configured for `docker-credential-pass` to work properly.
|
||||
It must be initialized with a `gpg2` key ID. Make sure your GPG key exists is in `gpg2` keyring as `pass` uses `gpg2` instead of the regular `gpg`.
|
||||
|
||||
## v0.5.2 (Mac OS X, Windows, Linux)
|
||||
|
||||
- Add a `version` command to output the version
|
||||
- Fix storing URLs without scheme, and use `https://` by default
|
||||
|
||||
## v0.5.1 (Go client, Mac OS X, Windows, Linux)
|
||||
|
||||
- Redirect credential helpers' standard error to the caller's
|
||||
- Prevent invalid credentials and credentials queries
|
||||
|
||||
## v0.5.0 (Mac OS X)
|
||||
|
||||
- Add a label for Docker credentials and filter credentials lookup to filter keychain lookups
|
||||
|
||||
## v0.4.2 (Mac OS X, Windows)
|
||||
|
||||
- Fix osxkeychain list
|
||||
- macOS binary is now signed on release
|
||||
- Generate a `.exe` instead
|
||||
|
||||
## v0.4.1 (Mac OS X)
|
||||
|
||||
- Fixes to support older version of OSX (10.10, 10.11)
|
||||
|
||||
## v0.4.0 (Go client, Mac OS X, Windows, Linux)
|
||||
|
||||
- Full implementation for OSX ready
|
||||
- Fix some windows issues
|
||||
- Implement client.List, change list API
|
||||
- mac: delete credentials before adding them to avoid already exist error (fixes #37)
|
||||
|
||||
## v0.3.0 (Go client)
|
||||
|
||||
- Add Go client library to talk with the native programs.
|
||||
|
||||
## v0.2.0 (Mac OS X, Windows, Linux)
|
||||
|
||||
- Initial release of docker-credential-secretservice for Linux.
|
||||
- Use new secrets payload introduced in https://github.com/docker/docker/pull/20970.
|
||||
|
||||
## v0.1.0 (Mac OS X, Windows)
|
||||
|
||||
- Initial release of docker-credential-osxkeychain for Mac OS X.
|
||||
- Initial release of docker-credential-wincred for Microsoft Windows.
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.21.6
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG OSXCROSS_VERSION=11.3-r7-debian
|
||||
ARG GOLANGCI_LINT_VERSION=v1.55.2
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ARG PACKAGE=github.com/docker/docker-credential-helpers
|
||||
|
||||
# xx is a helper for cross-compilation
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
# osxcross contains the MacOSX cross toolchain for xx
|
||||
FROM crazymax/osxcross:${OSXCROSS_VERSION} AS osxcross
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bullseye AS gobase
|
||||
COPY --from=xx / /
|
||||
ARG DEBIAN_FRONTEND
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends clang dpkg-dev file git lld llvm make pkg-config rsync
|
||||
ENV GOFLAGS="-mod=vendor"
|
||||
ENV CGO_ENABLED="1"
|
||||
WORKDIR /src
|
||||
|
||||
FROM gobase AS vendored
|
||||
RUN --mount=target=/context \
|
||||
--mount=target=.,type=tmpfs \
|
||||
--mount=target=/go/pkg/mod,type=cache <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
go mod tidy
|
||||
go mod vendor
|
||||
mkdir /out
|
||||
cp -r go.mod go.sum vendor /out
|
||||
EOT
|
||||
|
||||
FROM scratch AS vendor-update
|
||||
COPY --from=vendored /out /
|
||||
|
||||
FROM vendored AS vendor-validate
|
||||
RUN --mount=target=/context \
|
||||
--mount=target=.,type=tmpfs <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
git add -A
|
||||
rm -rf vendor
|
||||
cp -rf /out/* .
|
||||
if [ -n "$(git status --porcelain -- go.mod go.sum vendor)" ]; then
|
||||
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "make vendor"'
|
||||
git status --porcelain -- go.mod go.sum vendor
|
||||
exit 1
|
||||
fi
|
||||
EOT
|
||||
|
||||
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} AS golangci-lint
|
||||
FROM gobase AS lint
|
||||
ARG DEBIAN_FRONTEND
|
||||
RUN apt-get install -y binutils gcc libc6-dev libgcc-10-dev libsecret-1-dev pkg-config
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \
|
||||
golangci-lint run ./...
|
||||
|
||||
FROM gobase AS base
|
||||
ARG TARGETPLATFORM
|
||||
ARG DEBIAN_FRONTEND
|
||||
RUN xx-apt-get install -y binutils gcc libc6-dev libgcc-10-dev libsecret-1-dev pkg-config
|
||||
|
||||
FROM base AS test
|
||||
ARG DEBIAN_FRONTEND
|
||||
RUN xx-apt-get install -y dbus-x11 gnome-keyring gpg-agent gpgconf libsecret-1-dev pass
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod <<EOT
|
||||
set -e
|
||||
cp -r .github/workflows/fixtures /root/.gnupg
|
||||
gpg-connect-agent "RELOADAGENT" /bye
|
||||
gpg --import --batch --yes /root/.gnupg/7D851EB72D73BDA0.key
|
||||
gpg --update-trustdb
|
||||
echo '5\ny\n' | gpg --command-fd 0 --no-tty --edit-key 7D851EB72D73BDA0 trust
|
||||
gpg-connect-agent "PRESET_PASSPHRASE 3E2D1142AA59E08E16B7E2C64BA6DDC773B1A627 -1 77697468207374757069642070617373706872617365" /bye
|
||||
gpg-connect-agent "KEYINFO 3E2D1142AA59E08E16B7E2C64BA6DDC773B1A627" /bye
|
||||
gpg-connect-agent "PRESET_PASSPHRASE BA83FC8947213477F28ADC019F6564A956456163 -1 77697468207374757069642070617373706872617365" /bye
|
||||
gpg-connect-agent "KEYINFO BA83FC8947213477F28ADC019F6564A956456163" /bye
|
||||
pass init 7D851EB72D73BDA0
|
||||
gpg -k
|
||||
|
||||
mkdir /out
|
||||
xx-go --wrap
|
||||
make test COVERAGEDIR=/out
|
||||
EOT
|
||||
|
||||
FROM scratch AS test-coverage
|
||||
COPY --from=test /out /
|
||||
|
||||
FROM gobase AS version
|
||||
RUN --mount=target=. \
|
||||
echo -n "$(./hack/git-meta version)" | tee /tmp/.version ; echo -n "$(./hack/git-meta revision)" | tee /tmp/.revision
|
||||
|
||||
FROM base AS build-linux
|
||||
ARG PACKAGE
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \
|
||||
--mount=type=bind,source=/tmp/.revision,target=/tmp/.revision,from=version <<EOT
|
||||
set -ex
|
||||
xx-go --wrap
|
||||
make build-pass build-secretservice PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
|
||||
xx-verify /out/docker-credential-pass
|
||||
xx-verify /out/docker-credential-secretservice
|
||||
EOT
|
||||
|
||||
FROM base AS build-darwin
|
||||
ARG PACKAGE
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,from=osxcross,src=/osxsdk,target=/xx-sdk \
|
||||
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \
|
||||
--mount=type=bind,source=/tmp/.revision,target=/tmp/.revision,from=version <<EOT
|
||||
set -ex
|
||||
export MACOSX_VERSION_MIN=$(make print-MACOSX_DEPLOYMENT_TARGET)
|
||||
xx-go --wrap
|
||||
go install std
|
||||
make build-osxkeychain build-pass PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
|
||||
xx-verify /out/docker-credential-osxkeychain
|
||||
xx-verify /out/docker-credential-pass
|
||||
EOT
|
||||
|
||||
FROM base AS build-windows
|
||||
ARG PACKAGE
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \
|
||||
--mount=type=bind,source=/tmp/.revision,target=/tmp/.revision,from=version <<EOT
|
||||
set -ex
|
||||
xx-go --wrap
|
||||
make build-wincred PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
|
||||
mv /out/docker-credential-wincred /out/docker-credential-wincred.exe
|
||||
xx-verify /out/docker-credential-wincred.exe
|
||||
EOT
|
||||
|
||||
FROM build-$TARGETOS AS build
|
||||
|
||||
FROM scratch AS binaries
|
||||
COPY --from=build /out /
|
||||
|
||||
FROM --platform=$BUILDPLATFORM alpine AS releaser
|
||||
WORKDIR /work
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
RUN --mount=from=binaries \
|
||||
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version <<EOT
|
||||
set -e
|
||||
mkdir /out
|
||||
version="$(cat /tmp/.version)"
|
||||
[ "$TARGETOS" = "windows" ] && ext=".exe"
|
||||
for f in *; do
|
||||
cp "$f" "/out/${f%.*}-${version}.${TARGETOS}-${TARGETARCH}${TARGETVARIANT}${ext}"
|
||||
done
|
||||
EOT
|
||||
|
||||
FROM scratch AS release
|
||||
COPY --from=releaser /out/ /
|
||||
|
||||
FROM binaries
|
||||
Vendored
-83
@@ -1,83 +0,0 @@
|
||||
pipeline {
|
||||
agent none
|
||||
options {
|
||||
checkoutToSubdirectory('src/github.com/docker/docker-credential-helpers')
|
||||
}
|
||||
stages {
|
||||
stage('build') {
|
||||
parallel {
|
||||
stage('linux') {
|
||||
agent {
|
||||
kubernetes {
|
||||
label 'declarative'
|
||||
containerTemplate {
|
||||
name 'golang'
|
||||
image 'golang:1.12.4'
|
||||
ttyEnabled true
|
||||
command 'cat'
|
||||
}
|
||||
}
|
||||
}
|
||||
environment {
|
||||
GOPATH = pwd()
|
||||
PATH = "/usr/local/go/bin:${GOPATH}/bin:$PATH"
|
||||
}
|
||||
steps {
|
||||
container('golang') {
|
||||
dir('src/github.com/docker/docker-credential-helpers') {
|
||||
sh 'apt-get update && apt-get install -y libsecret-1-dev pass'
|
||||
sh 'make deps fmt lint test'
|
||||
sh 'make pass secretservice'
|
||||
sh 'make linuxrelease'
|
||||
archiveArtifacts 'release/docker-credential-*'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('mac') {
|
||||
agent {
|
||||
label 'mac-build && go1.12.4'
|
||||
}
|
||||
environment {
|
||||
PATH = "/usr/local/go/bin:${GOPATH}/bin:$PATH"
|
||||
GOPATH = pwd()
|
||||
}
|
||||
steps {
|
||||
dir('src/github.com/docker/docker-credential-helpers') {
|
||||
sh 'make deps fmt lint test'
|
||||
sh 'make osxcodesign'
|
||||
sh 'make osxrelease'
|
||||
archiveArtifacts 'release/docker-credential-*'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('windows') {
|
||||
agent {
|
||||
label 'win-build && go1.12.4'
|
||||
}
|
||||
environment {
|
||||
GOPATH = pwd()
|
||||
PATH = "${pwd()}/bin;$PATH"
|
||||
PFX = credentials('windows-build-2019-pfx')
|
||||
PFXPASSWORD = credentials('windows-build-2019-pfx-password')
|
||||
}
|
||||
steps {
|
||||
dir('src/github.com/docker/docker-credential-helpers') {
|
||||
sh 'echo ${PFX} | base64 -d > pfx'
|
||||
|
||||
sh 'make deps fmt lint test'
|
||||
sh 'make wincred'
|
||||
bat """ "C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x86\\signtool.exe" sign /fd SHA256 /a /f pfx /p ${PFXPASSWORD} /d Docker /du https://www.docker.com /t http://timestamp.verisign.com/scripts/timestamp.dll bin\\docker-credential-wincred.exe """
|
||||
archiveArtifacts 'bin/docker-credential-*'
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh 'rm -f pfx'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +1,86 @@
|
||||
.PHONY: all deps osxkeychain secretservice test validate wincred pass deb
|
||||
PACKAGE ?= github.com/docker/docker-credential-helpers
|
||||
VERSION ?= $(shell ./hack/git-meta version)
|
||||
REVISION ?= $(shell ./hack/git-meta revision)
|
||||
|
||||
TRAVIS_OS_NAME ?= linux
|
||||
VERSION := $(shell grep 'const Version' credentials/version.go | awk -F'"' '{ print $$2 }')
|
||||
GO_PKG = github.com/docker/docker-credential-helpers
|
||||
GO_LDFLAGS = -s -w -X ${GO_PKG}/credentials.Version=${VERSION} -X ${GO_PKG}/credentials.Revision=${REVISION} -X ${GO_PKG}/credentials.Package=${PACKAGE}
|
||||
|
||||
all: test
|
||||
BUILDX_CMD ?= docker buildx
|
||||
DESTDIR ?= ./bin/build
|
||||
COVERAGEDIR ?= ./bin/coverage
|
||||
|
||||
deps:
|
||||
go get -u golang.org/x/lint/golint
|
||||
# 10.11 is the minimum supported version for osxkeychain
|
||||
export MACOSX_DEPLOYMENT_TARGET = 10.11
|
||||
ifeq "$(shell go env GOOS)" "darwin"
|
||||
export CGO_CFLAGS = -mmacosx-version-min=$(MACOSX_DEPLOYMENT_TARGET)
|
||||
endif
|
||||
|
||||
.PHONY: all
|
||||
all: cross
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf bin
|
||||
rm -rf release
|
||||
|
||||
osxkeychain:
|
||||
mkdir -p bin
|
||||
go build -ldflags -s -o bin/docker-credential-osxkeychain osxkeychain/cmd/main_darwin.go
|
||||
.PHONY: build-%
|
||||
build-%: # build, can be one of build-osxkeychain build-pass build-secretservice build-wincred
|
||||
go build -trimpath -ldflags="$(GO_LDFLAGS) -X ${GO_PKG}/credentials.Name=docker-credential-$*" -o "$(DESTDIR)/docker-credential-$*" ./$*/cmd/
|
||||
|
||||
osxcodesign: osxkeychain
|
||||
$(eval SIGNINGHASH = $(shell security find-identity -v -p codesigning | grep "Developer ID Application: Docker Inc" | cut -d ' ' -f 4))
|
||||
xcrun -log codesign -s $(SIGNINGHASH) --force --verbose bin/docker-credential-osxkeychain
|
||||
xcrun codesign --verify --deep --strict --verbose=2 --display bin/docker-credential-osxkeychain
|
||||
# aliases for build-* targets
|
||||
.PHONY: osxkeychain secretservice pass wincred
|
||||
osxkeychain: build-osxkeychain
|
||||
secretservice: build-secretservice
|
||||
pass: build-pass
|
||||
wincred: build-wincred
|
||||
|
||||
secretservice:
|
||||
mkdir -p bin
|
||||
go build -o bin/docker-credential-secretservice secretservice/cmd/main_linux.go
|
||||
.PHONY: cross
|
||||
cross: # cross build all supported credential helpers
|
||||
$(BUILDX_CMD) bake binaries
|
||||
|
||||
pass:
|
||||
mkdir -p bin
|
||||
go build -o bin/docker-credential-pass pass/cmd/main.go
|
||||
|
||||
wincred:
|
||||
mkdir -p bin
|
||||
go build -o bin/docker-credential-wincred.exe wincred/cmd/main_windows.go
|
||||
|
||||
linuxrelease:
|
||||
mkdir -p release
|
||||
cd bin && tar cvfz ../release/docker-credential-pass-v$(VERSION)-amd64.tar.gz docker-credential-pass
|
||||
cd bin && tar cvfz ../release/docker-credential-secretservice-v$(VERSION)-amd64.tar.gz docker-credential-secretservice
|
||||
|
||||
osxrelease:
|
||||
mkdir -p release
|
||||
cd bin && tar cvfz ../release/docker-credential-osxkeychain-v$(VERSION)-amd64.tar.gz docker-credential-osxkeychain
|
||||
|
||||
winrelease:
|
||||
mkdir -p release
|
||||
cd bin && zip ../release/docker-credential-wincred-v$(VERSION)-amd64.zip docker-credential-wincred.exe
|
||||
.PHONY: release
|
||||
release: # create release
|
||||
./hack/release
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
# tests all packages except vendor
|
||||
go test -v `go list ./... | grep -v /vendor/`
|
||||
|
||||
vet: vet_$(TRAVIS_OS_NAME)
|
||||
go vet ./credentials
|
||||
|
||||
vet_win:
|
||||
go vet ./wincred
|
||||
|
||||
vet_osx:
|
||||
go vet ./osxkeychain
|
||||
|
||||
vet_linux:
|
||||
go vet ./secretservice
|
||||
mkdir -p $(COVERAGEDIR)
|
||||
go test -short -v -coverprofile=$(COVERAGEDIR)/coverage.txt -covermode=atomic ./...
|
||||
go tool cover -func=$(COVERAGEDIR)/coverage.txt
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
for p in `go list ./... | grep -v /vendor/`; do \
|
||||
golint $$p ; \
|
||||
done
|
||||
$(BUILDX_CMD) bake lint
|
||||
|
||||
.PHONY: validate-vendor
|
||||
validate-vendor:
|
||||
$(BUILDX_CMD) bake vendor-validate
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
gofmt -s -l `ls **/*.go | grep -v vendor`
|
||||
|
||||
validate: vet lint fmt
|
||||
|
||||
.PHONY: validate
|
||||
validate: lint validate-vendor fmt
|
||||
|
||||
BUILDIMG:=docker-credential-secretservice-$(VERSION)
|
||||
.PHONY: deb
|
||||
deb:
|
||||
mkdir -p release
|
||||
docker build -f deb/Dockerfile \
|
||||
--build-arg VERSION=$(VERSION) \
|
||||
--build-arg DISTRO=xenial \
|
||||
--build-arg VERSION=$(patsubst v%,%,$(VERSION)) \
|
||||
--build-arg REVISION=$(REVISION) \
|
||||
--tag $(BUILDIMG) \
|
||||
.
|
||||
docker run --rm --net=none $(BUILDIMG) tar cf - /release | tar xf -
|
||||
docker rmi $(BUILDIMG)
|
||||
|
||||
.PHONY: vendor
|
||||
vendor:
|
||||
$(eval $@_TMP_OUT := $(shell mktemp -d -t docker-output.XXXXXXXXXX))
|
||||
$(BUILDX_CMD) bake --set "*.output=type=local,dest=$($@_TMP_OUT)" vendor
|
||||
rm -rf ./vendor
|
||||
cp -R "$($@_TMP_OUT)"/* .
|
||||
rm -rf "$($@_TMP_OUT)"
|
||||
|
||||
.PHONY: print-%
|
||||
print-%: ; @echo $($*)
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
[](https://github.com/docker/docker-credential-helpers/releases/latest)
|
||||
[](https://pkg.go.dev/github.com/docker/docker-credential-helpers)
|
||||
[](https://github.com/docker/docker-credential-helpers/actions?query=workflow%3Abuild)
|
||||
[](https://codecov.io/gh/docker/docker-credential-helpers)
|
||||
[](https://goreportcard.com/report/github.com/docker/docker-credential-helpers)
|
||||
|
||||
## Introduction
|
||||
|
||||
docker-credential-helpers is a suite of programs to use native stores to keep Docker credentials safe.
|
||||
@@ -6,30 +12,52 @@ docker-credential-helpers is a suite of programs to use native stores to keep Do
|
||||
|
||||
Go to the [Releases](https://github.com/docker/docker-credential-helpers/releases) page and download the binary that works better for you. Put that binary in your `$PATH`, so Docker can find it.
|
||||
|
||||
### Building from scratch
|
||||
## Building
|
||||
|
||||
The programs in this repository are written with the Go programming language. These instructions assume that you have previous knowledge about the language and you have it installed in your machine.
|
||||
You can build the credential helpers using Docker:
|
||||
|
||||
1 - Download the source and put it in your `$GOPATH` with `go get`.
|
||||
```shell
|
||||
# install emulators
|
||||
$ docker run --privileged --rm tonistiigi/binfmt --install all
|
||||
|
||||
# create builder
|
||||
$ docker buildx create --use
|
||||
|
||||
# build credential helpers from remote repository and output to ./bin/build
|
||||
$ docker buildx bake "https://github.com/docker/docker-credential-helpers.git"
|
||||
|
||||
# or from local source
|
||||
$ git clone https://github.com/docker/docker-credential-helpers.git
|
||||
$ cd docker-credential-helpers
|
||||
$ docker buildx bake
|
||||
```
|
||||
$ go get github.com/docker/docker-credential-helpers
|
||||
|
||||
Or if the toolchain is already installed on your machine:
|
||||
|
||||
1 - Download the source.
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/docker/docker-credential-helpers.git
|
||||
$ cd docker-credential-helpers
|
||||
```
|
||||
|
||||
2 - Use `make` to build the program you want. That will leave an executable in the `bin` directory inside the repository.
|
||||
|
||||
```
|
||||
$ cd $GOPATH/docker/docker-credentials-helpers
|
||||
```shell
|
||||
$ make osxkeychain
|
||||
```
|
||||
|
||||
3 - Put that binary in your `$PATH`, so Docker can find it.
|
||||
|
||||
```shell
|
||||
$ cp bin/build/docker-credential-osxkeychain /usr/local/bin/
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### With the Docker Engine
|
||||
|
||||
Set the `credsStore` option in your `.docker/config.json` file with the suffix of the program you want to use. For instance, set it to `osxkeychain` if you want to use `docker-credential-osxkeychain`.
|
||||
Set the `credsStore` option in your `~/.docker/config.json` file with the suffix of the program you want to use. For instance, set it to `osxkeychain` if you want to use `docker-credential-osxkeychain`.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -73,8 +101,8 @@ A credential helper can be any program that can read values from the standard in
|
||||
|
||||
This repository also includes libraries to implement new credentials programs in Go. Adding a new helper program is pretty easy. You can see how the OS X keychain helper works in the [osxkeychain](osxkeychain) directory.
|
||||
|
||||
1. Implement the interface `credentials.Helper` in `YOUR_PACKAGE/YOUR_PACKAGE_$GOOS.go`
|
||||
2. Create a main program in `YOUR_PACKAGE/cmd/main_$GOOS.go`.
|
||||
1. Implement the interface `credentials.Helper` in `YOUR_PACKAGE/`
|
||||
2. Create a main program in `YOUR_PACKAGE/cmd/`.
|
||||
3. Add make tasks to build your program and run tests.
|
||||
|
||||
## License
|
||||
|
||||
+9
-16
@@ -16,17 +16,15 @@ func isValidCredsMessage(msg string) error {
|
||||
if credentials.IsCredentialsMissingServerURLMessage(msg) {
|
||||
return credentials.NewErrCredentialsMissingServerURL()
|
||||
}
|
||||
|
||||
if credentials.IsCredentialsMissingUsernameMessage(msg) {
|
||||
return credentials.NewErrCredentialsMissingUsername()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Store uses an external program to save credentials.
|
||||
func Store(program ProgramFunc, creds *credentials.Credentials) error {
|
||||
cmd := program("store")
|
||||
cmd := program(credentials.ActionStore)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(buffer).Encode(creds); err != nil {
|
||||
@@ -36,13 +34,10 @@ func Store(program ProgramFunc, creds *credentials.Credentials) error {
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||
if isValidErr := isValidCredsMessage(string(out)); isValidErr != nil {
|
||||
err = isValidErr
|
||||
}
|
||||
|
||||
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, t)
|
||||
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -50,22 +45,20 @@ func Store(program ProgramFunc, creds *credentials.Credentials) error {
|
||||
|
||||
// Get executes an external program to get the credentials from a native store.
|
||||
func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error) {
|
||||
cmd := program("get")
|
||||
cmd := program(credentials.ActionGet)
|
||||
cmd.Input(strings.NewReader(serverURL))
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
if credentials.IsErrCredentialsNotFoundMessage(t) {
|
||||
if credentials.IsErrCredentialsNotFoundMessage(string(out)) {
|
||||
return nil, credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||
if isValidErr := isValidCredsMessage(string(out)); isValidErr != nil {
|
||||
err = isValidErr
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, t)
|
||||
return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
|
||||
resp := &credentials.Credentials{
|
||||
@@ -81,7 +74,7 @@ func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error
|
||||
|
||||
// Erase executes a program to remove the server credentials from the native store.
|
||||
func Erase(program ProgramFunc, serverURL string) error {
|
||||
cmd := program("erase")
|
||||
cmd := program(credentials.ActionErase)
|
||||
cmd.Input(strings.NewReader(serverURL))
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
@@ -99,7 +92,7 @@ func Erase(program ProgramFunc, serverURL string) error {
|
||||
|
||||
// List executes a program to list server credentials in the native store.
|
||||
func List(program ProgramFunc) (map[string]string, error) {
|
||||
cmd := program("list")
|
||||
cmd := program(credentials.ActionList)
|
||||
cmd.Input(strings.NewReader("unused"))
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
|
||||
+41
-35
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -12,27 +11,27 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
validServerAddress = "https://index.docker.io/v1"
|
||||
validServerAddress = "https://registry.example.com/v1"
|
||||
validUsername = "linus"
|
||||
validServerAddress2 = "https://example.com:5002"
|
||||
invalidServerAddress = "https://foobar.example.com"
|
||||
missingCredsAddress = "https://missing.docker.io/v1"
|
||||
missingCredsAddress = "https://missing.example.com/v1"
|
||||
)
|
||||
|
||||
var errProgramExited = fmt.Errorf("exited 1")
|
||||
|
||||
// mockProgram simulates interactions between the docker client and a remote
|
||||
// credentials helper.
|
||||
// credentials-helper.
|
||||
// Unit tests inject this mocked command into the remote to control execution.
|
||||
type mockProgram struct {
|
||||
arg string
|
||||
input io.Reader
|
||||
}
|
||||
|
||||
// Output returns responses from the remote credentials helper.
|
||||
// Output returns responses from the remote credentials-helper.
|
||||
// It mocks those responses based in the input in the mock.
|
||||
func (m *mockProgram) Output() ([]byte, error) {
|
||||
in, err := ioutil.ReadAll(m.input)
|
||||
in, err := io.ReadAll(m.input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -81,7 +80,7 @@ func (m *mockProgram) Output() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errProgramExited
|
||||
}
|
||||
|
||||
// Input sets the input to send to a remote credentials helper.
|
||||
// Input sets the input to send to a remote credentials-helper.
|
||||
func (m *mockProgram) Input(in io.Reader) {
|
||||
m.input = in
|
||||
}
|
||||
@@ -93,57 +92,57 @@ func mockProgramFn(args ...string) Program {
|
||||
}
|
||||
|
||||
func ExampleStore() {
|
||||
p := NewShellProgramFunc("docker-credential-secretservice")
|
||||
p := NewShellProgramFunc("docker-credential-pass")
|
||||
|
||||
c := &credentials.Credentials{
|
||||
ServerURL: "https://example.com",
|
||||
Username: "calavera",
|
||||
ServerURL: "https://registry.example.com",
|
||||
Username: "exampleuser",
|
||||
Secret: "my super secret token",
|
||||
}
|
||||
|
||||
if err := Store(p, c); err != nil {
|
||||
fmt.Println(err)
|
||||
_, _ = fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
valid := []credentials.Credentials{
|
||||
{validServerAddress, "foo", "bar"},
|
||||
{validServerAddress2, "<token>", "abcd1234"},
|
||||
{ServerURL: validServerAddress, Username: "foo", Secret: "bar"},
|
||||
{ServerURL: validServerAddress2, Username: "<token>", Secret: "abcd1234"},
|
||||
}
|
||||
|
||||
for _, v := range valid {
|
||||
if err := Store(mockProgramFn, &v); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
invalid := []credentials.Credentials{
|
||||
{invalidServerAddress, "foo", "bar"},
|
||||
{ServerURL: invalidServerAddress, Username: "foo", Secret: "bar"},
|
||||
}
|
||||
|
||||
for _, v := range invalid {
|
||||
if err := Store(mockProgramFn, &v); err == nil {
|
||||
t.Fatalf("Expected error for server %s, got nil", v.ServerURL)
|
||||
t.Errorf("Expected error for server %s, got nil", v.ServerURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleGet() {
|
||||
p := NewShellProgramFunc("docker-credential-secretservice")
|
||||
p := NewShellProgramFunc("docker-credential-pass")
|
||||
|
||||
creds, err := Get(p, "https://example.com")
|
||||
creds, err := Get(p, "https://registry.example.com")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
_, _ = fmt.Println(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Got credentials for user `%s` in `%s`\n", creds.Username, creds.ServerURL)
|
||||
_, _ = fmt.Printf("Got credentials for user `%s` in `%s`\n", creds.Username, creds.ServerURL)
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
valid := []credentials.Credentials{
|
||||
{validServerAddress, "foo", "bar"},
|
||||
{validServerAddress2, "<token>", "abcd1234"},
|
||||
{ServerURL: validServerAddress, Username: "foo", Secret: "bar"},
|
||||
{ServerURL: validServerAddress2, Username: "<token>", Secret: "abcd1234"},
|
||||
}
|
||||
|
||||
for _, v := range valid {
|
||||
@@ -153,10 +152,10 @@ func TestGet(t *testing.T) {
|
||||
}
|
||||
|
||||
if c.Username != v.Username {
|
||||
t.Fatalf("expected username `%s`, got %s", v.Username, c.Username)
|
||||
t.Errorf("expected username `%s`, got %s", v.Username, c.Username)
|
||||
}
|
||||
if c.Secret != v.Secret {
|
||||
t.Fatalf("expected secret `%s`, got %s", v.Secret, c.Secret)
|
||||
t.Errorf("expected secret `%s`, got %s", v.Secret, c.Secret)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,10 +165,17 @@ func TestGet(t *testing.T) {
|
||||
serverURL string
|
||||
err string
|
||||
}{
|
||||
{missingCredsAddress, credentials.NewErrCredentialsNotFound().Error()},
|
||||
{invalidServerAddress, "error getting credentials - err: exited 1, out: `program failed`"},
|
||||
{"", fmt.Sprintf("error getting credentials - err: %s, out: `%s`",
|
||||
missingServerURLErr.Error(), missingServerURLErr.Error())},
|
||||
{
|
||||
serverURL: missingCredsAddress,
|
||||
err: credentials.NewErrCredentialsNotFound().Error(),
|
||||
},
|
||||
{
|
||||
serverURL: invalidServerAddress,
|
||||
err: "error getting credentials - err: exited 1, out: `program failed`",
|
||||
},
|
||||
{
|
||||
err: fmt.Sprintf("error getting credentials - err: %s, out: `%s`", missingServerURLErr.Error(), missingServerURLErr.Error()),
|
||||
},
|
||||
}
|
||||
|
||||
for _, v := range invalid {
|
||||
@@ -178,26 +184,26 @@ func TestGet(t *testing.T) {
|
||||
t.Fatalf("Expected error for server %s, got nil", v.serverURL)
|
||||
}
|
||||
if err.Error() != v.err {
|
||||
t.Fatalf("Expected error `%s`, got `%v`", v.err, err)
|
||||
t.Errorf("Expected error `%s`, got `%v`", v.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleErase() {
|
||||
p := NewShellProgramFunc("docker-credential-secretservice")
|
||||
p := NewShellProgramFunc("docker-credential-pass")
|
||||
|
||||
if err := Erase(p, "https://example.com"); err != nil {
|
||||
fmt.Println(err)
|
||||
if err := Erase(p, "https://registry.example.com"); err != nil {
|
||||
_, _ = fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErase(t *testing.T) {
|
||||
if err := Erase(mockProgramFn, validServerAddress); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := Erase(mockProgramFn, invalidServerAddress); err == nil {
|
||||
t.Fatalf("Expected error for server %s, got nil", invalidServerAddress)
|
||||
t.Errorf("Expected error for server %s, got nil", invalidServerAddress)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +214,6 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
|
||||
if username, exists := auths[validServerAddress]; !exists || username != validUsername {
|
||||
t.Fatalf("auths[%s] returned %s, %t; expected %s, %t", validServerAddress, username, exists, validUsername, true)
|
||||
t.Errorf("auths[%s] returned %s, %t; expected %s, %t", validServerAddress, username, exists, validUsername, true)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-8
@@ -1,11 +1,9 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
exec "golang.org/x/sys/execabs"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Program is an interface to execute external programs.
|
||||
@@ -31,27 +29,26 @@ func NewShellProgramFuncWithEnv(name string, env *map[string]string) ProgramFunc
|
||||
|
||||
func createProgramCmdRedirectErr(commandName string, args []string, env *map[string]string) *exec.Cmd {
|
||||
programCmd := exec.Command(commandName, args...)
|
||||
programCmd.Env = os.Environ()
|
||||
if env != nil {
|
||||
for k, v := range *env {
|
||||
programCmd.Env = append(programCmd.Env, fmt.Sprintf("%s=%s", k, v))
|
||||
programCmd.Env = append(programCmd.Environ(), k+"="+v)
|
||||
}
|
||||
}
|
||||
programCmd.Stderr = os.Stderr
|
||||
return programCmd
|
||||
}
|
||||
|
||||
// Shell invokes shell commands to talk with a remote credentials helper.
|
||||
// Shell invokes shell commands to talk with a remote credentials-helper.
|
||||
type Shell struct {
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
// Output returns responses from the remote credentials helper.
|
||||
// Output returns responses from the remote credentials-helper.
|
||||
func (s *Shell) Output() ([]byte, error) {
|
||||
return s.cmd.Output()
|
||||
}
|
||||
|
||||
// Input sets the input to send to a remote credentials helper.
|
||||
// Input sets the input to send to a remote credentials-helper.
|
||||
func (s *Shell) Input(in io.Reader) {
|
||||
s.cmd.Stdin = in
|
||||
}
|
||||
|
||||
+51
-28
@@ -10,6 +10,20 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Action defines the name of an action (sub-command) supported by a
|
||||
// credential-helper binary. It is an alias for "string", and mostly
|
||||
// for convenience.
|
||||
type Action = string
|
||||
|
||||
// List of actions (sub-commands) supported by credential-helper binaries.
|
||||
const (
|
||||
ActionStore Action = "store"
|
||||
ActionGet Action = "get"
|
||||
ActionErase Action = "erase"
|
||||
ActionList Action = "list"
|
||||
ActionVersion Action = "version"
|
||||
)
|
||||
|
||||
// Credentials holds the information shared between docker and the credentials store.
|
||||
type Credentials struct {
|
||||
ServerURL string
|
||||
@@ -43,42 +57,52 @@ func SetCredsLabel(label string) {
|
||||
CredsLabel = label
|
||||
}
|
||||
|
||||
// Serve initializes the credentials helper and parses the action argument.
|
||||
// Serve initializes the credentials-helper and parses the action argument.
|
||||
// This function is designed to be called from a command line interface.
|
||||
// It uses os.Args[1] as the key for the action.
|
||||
// It uses os.Stdin as input and os.Stdout as output.
|
||||
// This function terminates the program with os.Exit(1) if there is an error.
|
||||
func Serve(helper Helper) {
|
||||
var err error
|
||||
if len(os.Args) != 2 {
|
||||
err = fmt.Errorf("Usage: %s <store|get|erase|list|version>", os.Args[0])
|
||||
_, _ = fmt.Fprintln(os.Stdout, usage())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout)
|
||||
switch os.Args[1] {
|
||||
case "--version", "-v":
|
||||
_ = PrintVersion(os.Stdout)
|
||||
os.Exit(0)
|
||||
case "--help", "-h":
|
||||
_, _ = fmt.Fprintln(os.Stdout, usage())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stdout, "%v\n", err)
|
||||
if err := HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout); err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stdout, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleCommand uses a helper and a key to run a credential action.
|
||||
func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error {
|
||||
switch key {
|
||||
case "store":
|
||||
return Store(helper, in)
|
||||
case "get":
|
||||
return Get(helper, in, out)
|
||||
case "erase":
|
||||
return Erase(helper, in)
|
||||
case "list":
|
||||
return List(helper, out)
|
||||
case "version":
|
||||
return PrintVersion(out)
|
||||
func usage() string {
|
||||
return fmt.Sprintf("Usage: %s <store|get|erase|list|version>", Name)
|
||||
}
|
||||
|
||||
// HandleCommand runs a helper to execute a credential action.
|
||||
func HandleCommand(helper Helper, action Action, in io.Reader, out io.Writer) error {
|
||||
switch action {
|
||||
case ActionStore:
|
||||
return Store(helper, in)
|
||||
case ActionGet:
|
||||
return Get(helper, in, out)
|
||||
case ActionErase:
|
||||
return Erase(helper, in)
|
||||
case ActionList:
|
||||
return List(helper, out)
|
||||
case ActionVersion:
|
||||
return PrintVersion(out)
|
||||
default:
|
||||
return fmt.Errorf("%s: unknown action: %s", Name, action)
|
||||
}
|
||||
return fmt.Errorf("Unknown credential action `%s`", key)
|
||||
}
|
||||
|
||||
// Store uses a helper and an input reader to save credentials.
|
||||
@@ -132,18 +156,17 @@ func Get(helper Helper, reader io.Reader, writer io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := Credentials{
|
||||
buffer.Reset()
|
||||
err = json.NewEncoder(buffer).Encode(Credentials{
|
||||
ServerURL: serverURL,
|
||||
Username: username,
|
||||
Secret: secret,
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
if err := json.NewEncoder(buffer).Encode(resp); err != nil {
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(writer, buffer.String())
|
||||
_, _ = fmt.Fprint(writer, buffer.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -181,6 +204,6 @@ func List(helper Helper, writer io.Writer) error {
|
||||
|
||||
// PrintVersion outputs the current version.
|
||||
func PrintVersion(writer io.Writer) error {
|
||||
fmt.Fprintln(writer, Version)
|
||||
_, _ = fmt.Fprintf(writer, "%s (%s) %s\n", Name, Package, Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (m *memoryStore) List() (map[string]string, error) {
|
||||
}
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
serverURL := "https://index.docker.io/v1/"
|
||||
const serverURL = "https://registry.example.com/v1/"
|
||||
creds := &Credentials{
|
||||
ServerURL: serverURL,
|
||||
Username: "foo",
|
||||
@@ -65,11 +65,11 @@ func TestStore(t *testing.T) {
|
||||
}
|
||||
|
||||
if c.Username != "foo" {
|
||||
t.Fatalf("expected username foo, got %s\n", c.Username)
|
||||
t.Errorf("expected username foo, got %s\n", c.Username)
|
||||
}
|
||||
|
||||
if c.Secret != "bar" {
|
||||
t.Fatalf("expected username bar, got %s\n", c.Secret)
|
||||
t.Errorf("expected username bar, got %s\n", c.Secret)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,14 +88,14 @@ func TestStoreMissingServerURL(t *testing.T) {
|
||||
|
||||
h := newMemoryStore()
|
||||
|
||||
if err := Store(h, in); IsCredentialsMissingServerURL(err) == false {
|
||||
t.Fatal(err)
|
||||
if err := Store(h, in); !IsCredentialsMissingServerURL(err) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreMissingUsername(t *testing.T) {
|
||||
creds := &Credentials{
|
||||
ServerURL: "https://index.docker.io/v1/",
|
||||
ServerURL: "https://registry.example.com/v1/",
|
||||
Username: "",
|
||||
Secret: "bar",
|
||||
}
|
||||
@@ -108,13 +108,13 @@ func TestStoreMissingUsername(t *testing.T) {
|
||||
|
||||
h := newMemoryStore()
|
||||
|
||||
if err := Store(h, in); IsCredentialsMissingUsername(err) == false {
|
||||
t.Fatal(err)
|
||||
if err := Store(h, in); !IsCredentialsMissingUsername(err) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
serverURL := "https://index.docker.io/v1/"
|
||||
const serverURL = "https://registry.example.com/v1/"
|
||||
creds := &Credentials{
|
||||
ServerURL: serverURL,
|
||||
Username: "foo",
|
||||
@@ -147,16 +147,16 @@ func TestGet(t *testing.T) {
|
||||
}
|
||||
|
||||
if c.Username != "foo" {
|
||||
t.Fatalf("expected username foo, got %s\n", c.Username)
|
||||
t.Errorf("expected username foo, got %s\n", c.Username)
|
||||
}
|
||||
|
||||
if c.Secret != "bar" {
|
||||
t.Fatalf("expected username bar, got %s\n", c.Secret)
|
||||
t.Errorf("expected username bar, got %s\n", c.Secret)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMissingServerURL(t *testing.T) {
|
||||
serverURL := "https://index.docker.io/v1/"
|
||||
const serverURL = "https://registry.example.com/v1/"
|
||||
creds := &Credentials{
|
||||
ServerURL: serverURL,
|
||||
Username: "foo",
|
||||
@@ -176,13 +176,13 @@ func TestGetMissingServerURL(t *testing.T) {
|
||||
buf := strings.NewReader("")
|
||||
w := new(bytes.Buffer)
|
||||
|
||||
if err := Get(h, buf, w); IsCredentialsMissingServerURL(err) == false {
|
||||
t.Fatal(err)
|
||||
if err := Get(h, buf, w); !IsCredentialsMissingServerURL(err) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErase(t *testing.T) {
|
||||
serverURL := "https://index.docker.io/v1/"
|
||||
const serverURL = "https://registry.example.com/v1/"
|
||||
creds := &Credentials{
|
||||
ServerURL: serverURL,
|
||||
Username: "foo",
|
||||
@@ -206,12 +206,12 @@ func TestErase(t *testing.T) {
|
||||
|
||||
w := new(bytes.Buffer)
|
||||
if err := Get(h, buf, w); err == nil {
|
||||
t.Fatal("expected error getting missing creds, got empty")
|
||||
t.Error("expected error getting missing creds, got empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEraseMissingServerURL(t *testing.T) {
|
||||
serverURL := "https://index.docker.io/v1/"
|
||||
const serverURL = "https://registry.example.com/v1/"
|
||||
creds := &Credentials{
|
||||
ServerURL: serverURL,
|
||||
Username: "foo",
|
||||
@@ -229,8 +229,8 @@ func TestEraseMissingServerURL(t *testing.T) {
|
||||
}
|
||||
|
||||
buf := strings.NewReader("")
|
||||
if err := Erase(h, buf); IsCredentialsMissingServerURL(err) == false {
|
||||
t.Fatal(err)
|
||||
if err := Erase(h, buf); !IsCredentialsMissingServerURL(err) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +244,6 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
// testing that there is an output
|
||||
if out.Len() == 0 {
|
||||
t.Fatalf("expected output in the writer, got %d", 0)
|
||||
t.Error("expected output in the writer, got 0")
|
||||
}
|
||||
}
|
||||
|
||||
+31
-9
@@ -1,5 +1,10 @@
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrCredentialsNotFound standardizes the not found error, so every helper returns
|
||||
// the same message and docker can handle it properly.
|
||||
@@ -21,6 +26,11 @@ func (errCredentialsNotFound) Error() string {
|
||||
return errCredentialsNotFoundMessage
|
||||
}
|
||||
|
||||
// NotFound implements the [ErrNotFound][errdefs.ErrNotFound] interface.
|
||||
//
|
||||
// [errdefs.ErrNotFound]: https://pkg.go.dev/github.com/docker/docker@v24.0.1+incompatible/errdefs#ErrNotFound
|
||||
func (errCredentialsNotFound) NotFound() {}
|
||||
|
||||
// NewErrCredentialsNotFound creates a new error
|
||||
// for when the credentials are not in the store.
|
||||
func NewErrCredentialsNotFound() error {
|
||||
@@ -30,8 +40,8 @@ func NewErrCredentialsNotFound() error {
|
||||
// IsErrCredentialsNotFound returns true if the error
|
||||
// was caused by not having a set of credentials in a store.
|
||||
func IsErrCredentialsNotFound(err error) bool {
|
||||
_, ok := err.(errCredentialsNotFound)
|
||||
return ok
|
||||
var target errCredentialsNotFound
|
||||
return errors.As(err, &target)
|
||||
}
|
||||
|
||||
// IsErrCredentialsNotFoundMessage returns true if the error
|
||||
@@ -40,7 +50,7 @@ func IsErrCredentialsNotFound(err error) bool {
|
||||
// This function helps to check messages returned by an
|
||||
// external program via its standard output.
|
||||
func IsErrCredentialsNotFoundMessage(err string) bool {
|
||||
return err == errCredentialsNotFoundMessage
|
||||
return strings.TrimSpace(err) == errCredentialsNotFoundMessage
|
||||
}
|
||||
|
||||
// errCredentialsMissingServerURL represents an error raised
|
||||
@@ -53,6 +63,12 @@ func (errCredentialsMissingServerURL) Error() string {
|
||||
return errCredentialsMissingServerURLMessage
|
||||
}
|
||||
|
||||
// InvalidParameter implements the [ErrInvalidParameter][errdefs.ErrInvalidParameter]
|
||||
// interface.
|
||||
//
|
||||
// [errdefs.ErrInvalidParameter]: https://pkg.go.dev/github.com/docker/docker@v24.0.1+incompatible/errdefs#ErrInvalidParameter
|
||||
func (errCredentialsMissingServerURL) InvalidParameter() {}
|
||||
|
||||
// errCredentialsMissingUsername represents an error raised
|
||||
// when the credentials object has no username or when no
|
||||
// username is provided to a credentials operation requiring
|
||||
@@ -63,6 +79,12 @@ func (errCredentialsMissingUsername) Error() string {
|
||||
return errCredentialsMissingUsernameMessage
|
||||
}
|
||||
|
||||
// InvalidParameter implements the [ErrInvalidParameter][errdefs.ErrInvalidParameter]
|
||||
// interface.
|
||||
//
|
||||
// [errdefs.ErrInvalidParameter]: https://pkg.go.dev/github.com/docker/docker@v24.0.1+incompatible/errdefs#ErrInvalidParameter
|
||||
func (errCredentialsMissingUsername) InvalidParameter() {}
|
||||
|
||||
// NewErrCredentialsMissingServerURL creates a new error for
|
||||
// errCredentialsMissingServerURL.
|
||||
func NewErrCredentialsMissingServerURL() error {
|
||||
@@ -78,25 +100,25 @@ func NewErrCredentialsMissingUsername() error {
|
||||
// IsCredentialsMissingServerURL returns true if the error
|
||||
// was an errCredentialsMissingServerURL.
|
||||
func IsCredentialsMissingServerURL(err error) bool {
|
||||
_, ok := err.(errCredentialsMissingServerURL)
|
||||
return ok
|
||||
var target errCredentialsMissingServerURL
|
||||
return errors.As(err, &target)
|
||||
}
|
||||
|
||||
// IsCredentialsMissingServerURLMessage checks for an
|
||||
// errCredentialsMissingServerURL in the error message.
|
||||
func IsCredentialsMissingServerURLMessage(err string) bool {
|
||||
return err == errCredentialsMissingServerURLMessage
|
||||
return strings.TrimSpace(err) == errCredentialsMissingServerURLMessage
|
||||
}
|
||||
|
||||
// IsCredentialsMissingUsername returns true if the error
|
||||
// was an errCredentialsMissingUsername.
|
||||
func IsCredentialsMissingUsername(err error) bool {
|
||||
_, ok := err.(errCredentialsMissingUsername)
|
||||
return ok
|
||||
var target errCredentialsMissingUsername
|
||||
return errors.As(err, &target)
|
||||
}
|
||||
|
||||
// IsCredentialsMissingUsernameMessage checks for an
|
||||
// errCredentialsMissingUsername in the error message.
|
||||
func IsCredentialsMissingUsernameMessage(err string) bool {
|
||||
return err == errCredentialsMissingUsernameMessage
|
||||
return strings.TrimSpace(err) == errCredentialsMissingUsernameMessage
|
||||
}
|
||||
|
||||
+14
-2
@@ -1,4 +1,16 @@
|
||||
package credentials
|
||||
|
||||
// Version holds a string describing the current version
|
||||
const Version = "0.6.4"
|
||||
var (
|
||||
// Name is filled at linking time
|
||||
Name = ""
|
||||
|
||||
// Package is filled at linking time
|
||||
Package = "github.com/docker/docker-credential-helpers"
|
||||
|
||||
// Version holds the complete version number. Filled in at linking time.
|
||||
Version = "v0.0.0+unknown"
|
||||
|
||||
// Revision is filled with the VCS (e.g. git) revision being used to build
|
||||
// the program at linking time.
|
||||
Revision = ""
|
||||
)
|
||||
|
||||
+24
-6
@@ -1,13 +1,23 @@
|
||||
FROM ubuntu:xenial
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG VERSION
|
||||
ARG DISTRO
|
||||
ARG GO_VERSION=1.21.6
|
||||
ARG DISTRO=ubuntu
|
||||
ARG SUITE=focal
|
||||
|
||||
RUN apt-get update && apt-get install -yy debhelper dh-make golang-go libsecret-1-dev
|
||||
FROM golang:${GO_VERSION}-bullseye AS golang
|
||||
|
||||
FROM ${DISTRO}:${SUITE}
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -yy debhelper dh-make libsecret-1-dev
|
||||
RUN mkdir -p /build
|
||||
|
||||
WORKDIR /build
|
||||
ENV GOPATH /build
|
||||
ENV GOPROXY=https://proxy.golang.org|direct
|
||||
ENV GO111MODULE=off
|
||||
ENV GOPATH=/build
|
||||
ENV PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
|
||||
COPY --from=golang /usr/local/go /usr/local/go
|
||||
|
||||
COPY Makefile .
|
||||
COPY credentials credentials
|
||||
@@ -16,4 +26,12 @@ COPY pass pass
|
||||
COPY deb/debian ./debian
|
||||
COPY deb/build-deb .
|
||||
|
||||
RUN /build/build-deb ${VERSION} ${DISTRO}
|
||||
ARG VERSION
|
||||
ENV VERSION=${VERSION}
|
||||
ARG REVISION
|
||||
ENV REVISION=${REVISION}
|
||||
ARG DISTRO
|
||||
ENV DISTRO=${DISTRO}
|
||||
ARG SUITE
|
||||
ENV SUITE=${SUITE}
|
||||
RUN /build/build-deb
|
||||
|
||||
+2
-6
@@ -1,14 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -x
|
||||
set -e
|
||||
|
||||
version=$1
|
||||
distro=$2
|
||||
set -ex
|
||||
|
||||
maintainer=$(awk -F ': ' '$1 == "Maintainer" { print $2; exit }' debian/control)
|
||||
|
||||
cat > "debian/changelog" <<-EOF
|
||||
docker-credential-helpers ($version) $DISTRO; urgency=low
|
||||
docker-credential-helpers ($VERSION) $DISTRO-$SUITE; urgency=low
|
||||
|
||||
* New upstream version
|
||||
|
||||
|
||||
+2
-1
@@ -3,7 +3,8 @@
|
||||
DESTDIR := $(CURDIR)/debian/tmp
|
||||
|
||||
override_dh_auto_build:
|
||||
make secretservice pass
|
||||
make build-secretservice DESTDIR=bin
|
||||
make build-pass DESTDIR=bin
|
||||
|
||||
override_dh_auto_install:
|
||||
install -D bin/docker-credential-secretservice $(DESTDIR)/usr/bin/docker-credential-secretservice
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
variable "GO_VERSION" {
|
||||
default = "1.21.6"
|
||||
}
|
||||
|
||||
# Defines the output folder
|
||||
variable "DESTDIR" {
|
||||
default = ""
|
||||
}
|
||||
function "bindir" {
|
||||
params = [defaultdir]
|
||||
result = DESTDIR != "" ? DESTDIR : "./bin/${defaultdir}"
|
||||
}
|
||||
|
||||
target "_common" {
|
||||
args = {
|
||||
GO_VERSION = GO_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["binaries"]
|
||||
}
|
||||
|
||||
group "validate" {
|
||||
targets = ["lint", "vendor-validate"]
|
||||
}
|
||||
|
||||
target "lint" {
|
||||
inherits = ["_common"]
|
||||
target = "lint"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "vendor-validate" {
|
||||
inherits = ["_common"]
|
||||
target = "vendor-validate"
|
||||
output = ["type=cacheonly"]
|
||||
}
|
||||
|
||||
target "vendor" {
|
||||
inherits = ["_common"]
|
||||
target = "vendor-update"
|
||||
output = ["."]
|
||||
}
|
||||
|
||||
target "test" {
|
||||
inherits = ["_common"]
|
||||
target = "test-coverage"
|
||||
output = [bindir("coverage")]
|
||||
}
|
||||
|
||||
target "binaries" {
|
||||
inherits = ["_common"]
|
||||
target = "binaries"
|
||||
output = [bindir("build")]
|
||||
platforms = [
|
||||
"darwin/amd64",
|
||||
"darwin/arm64",
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
"linux/arm/v7",
|
||||
"linux/arm/v6",
|
||||
"linux/ppc64le",
|
||||
"linux/s390x",
|
||||
"windows/amd64",
|
||||
"windows/arm64"
|
||||
]
|
||||
}
|
||||
|
||||
target "release" {
|
||||
inherits = ["binaries"]
|
||||
target = "release"
|
||||
output = [bindir("release")]
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
module github.com/docker/docker-credential-helpers
|
||||
|
||||
go 1.13
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/danieljoos/wincred v1.1.0
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4
|
||||
)
|
||||
require github.com/danieljoos/wincred v1.2.1
|
||||
|
||||
require golang.org/x/sys v0.15.0 // indirect
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
|
||||
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs=
|
||||
github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
set -e
|
||||
|
||||
case $1 in
|
||||
"version")
|
||||
git describe --match 'v[0-9]*' --dirty='.m' --always --tags
|
||||
;;
|
||||
"revision")
|
||||
echo "$(git rev-parse HEAD)$(if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)"
|
||||
;;
|
||||
*)
|
||||
echo "usage: ./hack/git-meta <version|revision>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
: "${BUILDX_CMD=docker buildx}"
|
||||
: "${DESTDIR=./bin/release}"
|
||||
: "${CACHE_FROM=}"
|
||||
: "${CACHE_TO=}"
|
||||
|
||||
: "${SIGN=}"
|
||||
: "${PFX=}"
|
||||
: "${PFXPASSWORD=}"
|
||||
|
||||
if [ -n "$CACHE_FROM" ]; then
|
||||
for cfrom in $CACHE_FROM; do
|
||||
cacheFlags+=(--set "*.cache-from=$cfrom")
|
||||
done
|
||||
fi
|
||||
if [ -n "$CACHE_TO" ]; then
|
||||
for cto in $CACHE_TO; do
|
||||
cacheFlags+=(--set "*.cache-to=$cto")
|
||||
done
|
||||
fi
|
||||
|
||||
dockerpfx=$(mktemp -t dockercredhelper-pfx.XXXXXXXXXX)
|
||||
function clean {
|
||||
rm -f "$dockerpfx"
|
||||
}
|
||||
trap clean EXIT
|
||||
|
||||
# release
|
||||
(
|
||||
set -x
|
||||
${BUILDX_CMD} bake "${cacheFlags[@]}" --set "*.output=$DESTDIR" release
|
||||
)
|
||||
|
||||
# wrap binaries
|
||||
mv -f ./${DESTDIR}/**/* ./${DESTDIR}/
|
||||
find ./${DESTDIR} -type d -empty -delete
|
||||
|
||||
# sign binaries
|
||||
if [ -n "$SIGN" ]; then
|
||||
for f in "${DESTDIR}"/*".darwin-"*; do
|
||||
SIGNINGHASH=$(security find-identity -v -p codesigning | grep "Developer ID Application: Docker Inc" | cut -d ' ' -f 4)
|
||||
xcrun -log codesign -s "$SIGNINGHASH" --force --verbose "$f"
|
||||
xcrun codesign --verify --deep --strict --verbose=2 --display "$f"
|
||||
done
|
||||
for f in "${DESTDIR}"/*".windows-"*; do
|
||||
echo ${PFX} | base64 -d > "$dockerpfx"
|
||||
signtool sign /fd SHA256 /a /f pfx /p ${PFXPASSWORD} /d Docker /du https://www.docker.com /t http://timestamp.verisign.com/scripts/timestamp.dll "$f"
|
||||
done
|
||||
fi
|
||||
|
||||
# checksums
|
||||
(
|
||||
cd ${DESTDIR}
|
||||
sha256sum -b docker-credential-* > ./checksums.txt
|
||||
sha256sum -c --strict checksums.txt
|
||||
)
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build darwin && cgo
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "osxkeychain_darwin.h"
|
||||
#include "osxkeychain.h"
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Foundation/NSValue.h>
|
||||
#include <stdio.h>
|
||||
@@ -1,13 +1,16 @@
|
||||
//go:build darwin && cgo
|
||||
|
||||
package osxkeychain
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c -mmacosx-version-min=10.11
|
||||
#cgo LDFLAGS: -framework Security -framework Foundation -mmacosx-version-min=10.11
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Security -framework Foundation
|
||||
|
||||
#include "osxkeychain_darwin.h"
|
||||
#include "osxkeychain.h"
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
@@ -33,7 +36,7 @@ type Osxkeychain struct{}
|
||||
|
||||
// Add adds new credentials to the keychain.
|
||||
func (h Osxkeychain) Add(creds *credentials.Credentials) error {
|
||||
h.Delete(creds.ServerURL)
|
||||
_ = h.Delete(creds.ServerURL) // ignore errors as existing credential may not exist.
|
||||
|
||||
s, err := splitServer(creds.ServerURL)
|
||||
if err != nil {
|
||||
@@ -65,10 +68,16 @@ func (h Osxkeychain) Delete(serverURL string) error {
|
||||
}
|
||||
defer freeServer(s)
|
||||
|
||||
errMsg := C.keychain_delete(s)
|
||||
if errMsg != nil {
|
||||
if errMsg := C.keychain_delete(s); errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
return errors.New(C.GoString(errMsg))
|
||||
switch goMsg := C.GoString(errMsg); goMsg {
|
||||
case errCredentialsNotFound:
|
||||
return credentials.NewErrCredentialsNotFound()
|
||||
case errInteractionNotAllowed:
|
||||
return ErrInteractionNotAllowed
|
||||
default:
|
||||
return errors.New(goMsg)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -92,16 +101,15 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) {
|
||||
errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret)
|
||||
if errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
goMsg := C.GoString(errMsg)
|
||||
if goMsg == errCredentialsNotFound {
|
||||
switch goMsg := C.GoString(errMsg); goMsg {
|
||||
case errCredentialsNotFound:
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
if goMsg == errInteractionNotAllowed {
|
||||
case errInteractionNotAllowed:
|
||||
return "", "", ErrInteractionNotAllowed
|
||||
}
|
||||
|
||||
default:
|
||||
return "", "", errors.New(goMsg)
|
||||
}
|
||||
}
|
||||
|
||||
user := C.GoStringN(username, C.int(usernameLen))
|
||||
pass := C.GoStringN(secret, C.int(secretLen))
|
||||
@@ -123,16 +131,15 @@ func (h Osxkeychain) List() (map[string]string, error) {
|
||||
defer C.freeListData(&acctsC, listLenC)
|
||||
if errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
goMsg := C.GoString(errMsg)
|
||||
if goMsg == errCredentialsNotFound {
|
||||
switch goMsg := C.GoString(errMsg); goMsg {
|
||||
case errCredentialsNotFound:
|
||||
return make(map[string]string), nil
|
||||
}
|
||||
if goMsg == errInteractionNotAllowed {
|
||||
case errInteractionNotAllowed:
|
||||
return nil, ErrInteractionNotAllowed
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, errors.New(goMsg)
|
||||
}
|
||||
}
|
||||
|
||||
var listLen int
|
||||
listLen = int(listLenC)
|
||||
@@ -160,7 +167,7 @@ func splitServer(serverURL string) (*C.struct_Server, error) {
|
||||
proto = C.kSecProtocolTypeHTTP
|
||||
}
|
||||
var port int
|
||||
p := registryurl.GetPort(u)
|
||||
p := u.Port()
|
||||
if p != "" {
|
||||
port, err = strconv.Atoi(p)
|
||||
if err != nil {
|
||||
@@ -170,7 +177,7 @@ func splitServer(serverURL string) (*C.struct_Server, error) {
|
||||
|
||||
return &C.struct_Server{
|
||||
proto: C.SecProtocolType(proto),
|
||||
host: C.CString(registryurl.GetHostname(u)),
|
||||
host: C.CString(u.Hostname()),
|
||||
port: C.uint(port),
|
||||
path: C.CString(u.Path),
|
||||
}, nil
|
||||
@@ -1,213 +0,0 @@
|
||||
package osxkeychain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
func TestOSXKeychainHelper(t *testing.T) {
|
||||
creds := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.docker.io:2376/v1",
|
||||
Username: "foobar",
|
||||
Secret: "foobarbaz",
|
||||
}
|
||||
creds1 := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.docker.io:2376/v2",
|
||||
Username: "foobarbaz",
|
||||
Secret: "foobar",
|
||||
}
|
||||
helper := Osxkeychain{}
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
username, secret, err := helper.Get(creds.ServerURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if username != "foobar" {
|
||||
t.Fatalf("expected %s, got %s\n", "foobar", username)
|
||||
}
|
||||
|
||||
if secret != "foobarbaz" {
|
||||
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
|
||||
}
|
||||
|
||||
auths, err := helper.List()
|
||||
if err != nil || len(auths) == 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
helper.Add(creds1)
|
||||
defer helper.Delete(creds1.ServerURL)
|
||||
newauths, err := helper.List()
|
||||
if len(newauths)-len(auths) != 1 {
|
||||
if err == nil {
|
||||
t.Fatalf("Error: len(newauths): %d, len(auths): %d", len(newauths), len(auths))
|
||||
}
|
||||
t.Fatalf("Error: len(newauths): %d, len(auths): %d\n Error= %v", len(newauths), len(auths), err)
|
||||
}
|
||||
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSXKeychainHelperRetrieveAliases verifies that secrets can be accessed
|
||||
// through variations on the URL
|
||||
func TestOSXKeychainHelperRetrieveAliases(t *testing.T) {
|
||||
tests := []struct {
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
// stored with port, retrieved without
|
||||
{"https://foobar.docker.io:2376", "https://foobar.docker.io"},
|
||||
|
||||
// stored as https, retrieved without scheme
|
||||
{"https://foobar.docker.io:2376", "foobar.docker.io"},
|
||||
|
||||
// stored with path, retrieved without
|
||||
{"https://foobar.docker.io:1234/one/two", "https://foobar.docker.io:1234"},
|
||||
}
|
||||
|
||||
helper := Osxkeychain{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
|
||||
continue
|
||||
}
|
||||
if _, _, err := helper.Get(te.readURL); err != nil {
|
||||
t.Errorf("Error: failed to read secret for URL %q using %q", te.storeURL, te.readURL)
|
||||
}
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSXKeychainHelperRetrieveStrict verifies that only matching secrets are
|
||||
// returned.
|
||||
func TestOSXKeychainHelperRetrieveStrict(t *testing.T) {
|
||||
tests := []struct {
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
// stored as https, retrieved using http
|
||||
{"https://foobar.docker.io:2376", "http://foobar.docker.io:2376"},
|
||||
|
||||
// stored as http, retrieved using https
|
||||
{"http://foobar.docker.io:2376", "https://foobar.docker.io:2376"},
|
||||
|
||||
// same: stored as http, retrieved without a scheme specified (hence, using the default https://)
|
||||
{"http://foobar.docker.io", "foobar.docker.io:5678"},
|
||||
|
||||
// non-matching ports
|
||||
{"https://foobar.docker.io:1234", "https://foobar.docker.io:5678"},
|
||||
|
||||
// non-matching ports TODO is this desired behavior? The other way round does work
|
||||
//{"https://foobar.docker.io", "https://foobar.docker.io:5678"},
|
||||
|
||||
// non-matching paths
|
||||
{"https://foobar.docker.io:1234/one/two", "https://foobar.docker.io:1234/five/six"},
|
||||
}
|
||||
|
||||
helper := Osxkeychain{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
|
||||
continue
|
||||
}
|
||||
if _, _, err := helper.Get(te.readURL); err == nil {
|
||||
t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", te.storeURL, te.readURL)
|
||||
}
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSXKeychainHelperStoreRetrieve verifies that secrets stored in the
|
||||
// the keychain can be read back using the URL that was used to store them.
|
||||
func TestOSXKeychainHelperStoreRetrieve(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
}{
|
||||
{url: "foobar.docker.io"},
|
||||
{url: "foobar.docker.io:2376"},
|
||||
{url: "//foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376"},
|
||||
{url: "http://foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376/some/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar"},
|
||||
}
|
||||
|
||||
helper := Osxkeychain{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.url)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.url)
|
||||
}
|
||||
|
||||
// Note that we don't delete between individual tests here, to verify that
|
||||
// subsequent stores/overwrites don't affect storing / retrieving secrets.
|
||||
for i, te := range tests {
|
||||
c := &credentials.Credentials{
|
||||
ServerURL: te.url,
|
||||
Username: fmt.Sprintf("user-%d", i),
|
||||
Secret: fmt.Sprintf("secret-%d", i),
|
||||
}
|
||||
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL: %s: %s", te.url, err)
|
||||
continue
|
||||
}
|
||||
user, secret, err := helper.Get(te.url)
|
||||
if err != nil {
|
||||
t.Errorf("Error: failed to read secret for URL %q: %s", te.url, err)
|
||||
continue
|
||||
}
|
||||
if user != c.Username {
|
||||
t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, te.url)
|
||||
}
|
||||
if secret != c.Secret {
|
||||
t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, te.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingCredentials(t *testing.T) {
|
||||
helper := Osxkeychain{}
|
||||
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
|
||||
if !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
//go:build darwin && cgo
|
||||
|
||||
package osxkeychain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
func TestOSXKeychainHelper(t *testing.T) {
|
||||
creds := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.example.com:2376/v1",
|
||||
Username: "foobar",
|
||||
Secret: "foobarbaz",
|
||||
}
|
||||
creds1 := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.example.com:2376/v2",
|
||||
Username: "foobarbaz",
|
||||
Secret: "foobar",
|
||||
}
|
||||
helper := Osxkeychain{}
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
username, secret, err := helper.Get(creds.ServerURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if username != "foobar" {
|
||||
t.Fatalf("expected %s, got %s\n", "foobar", username)
|
||||
}
|
||||
|
||||
if secret != "foobarbaz" {
|
||||
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
|
||||
}
|
||||
|
||||
auths, err := helper.List()
|
||||
if err != nil || len(auths) == 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
helper.Add(creds1)
|
||||
defer helper.Delete(creds1.ServerURL)
|
||||
newauths, err := helper.List()
|
||||
if len(newauths)-len(auths) != 1 {
|
||||
if err == nil {
|
||||
t.Fatalf("Error: len(newauths): %d, len(auths): %d", len(newauths), len(auths))
|
||||
}
|
||||
t.Fatalf("Error: len(newauths): %d, len(auths): %d\n Error= %v", len(newauths), len(auths), err)
|
||||
}
|
||||
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSXKeychainHelperRetrieveAliases verifies that secrets can be accessed
|
||||
// through variations on the URL
|
||||
func TestOSXKeychainHelperRetrieveAliases(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
{
|
||||
doc: "stored with port, retrieved without",
|
||||
storeURL: "https://foobar.example.com:2376",
|
||||
readURL: "https://foobar.example.com",
|
||||
},
|
||||
{
|
||||
doc: "stored as https, retrieved without scheme",
|
||||
storeURL: "https://foobar.example.com:2376",
|
||||
readURL: "foobar.example.com",
|
||||
},
|
||||
{
|
||||
doc: "stored with path, retrieved without",
|
||||
storeURL: "https://foobar.example.com:1234/one/two",
|
||||
readURL: "https://foobar.example.com:1234",
|
||||
},
|
||||
}
|
||||
|
||||
helper := Osxkeychain{}
|
||||
t.Cleanup(func() {
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Clean store before testing.
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err)
|
||||
}
|
||||
if _, _, err := helper.Get(tc.readURL); err != nil {
|
||||
t.Errorf("Error: failed to read secret for URL %q using %q", tc.storeURL, tc.readURL)
|
||||
}
|
||||
if err := helper.Delete(tc.storeURL); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSXKeychainHelperRetrieveStrict verifies that only matching secrets are
|
||||
// returned.
|
||||
func TestOSXKeychainHelperRetrieveStrict(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
{
|
||||
doc: "stored as https, retrieved using http",
|
||||
storeURL: "https://foobar.example.com:2376",
|
||||
readURL: "http://foobar.example.com:2376",
|
||||
},
|
||||
{
|
||||
doc: "stored as http, retrieved using https",
|
||||
storeURL: "http://foobar.example.com:2376",
|
||||
readURL: "https://foobar.example.com:2376",
|
||||
},
|
||||
{
|
||||
// stored as http, retrieved without a scheme specified (hence, using the default https://)
|
||||
doc: "stored as http, retrieved without scheme",
|
||||
storeURL: "http://foobar.example.com",
|
||||
readURL: "foobar.example.com:5678",
|
||||
},
|
||||
{
|
||||
doc: "non-matching ports",
|
||||
storeURL: "https://foobar.example.com:1234",
|
||||
readURL: "https://foobar.example.com:5678",
|
||||
},
|
||||
// TODO: is this desired behavior? The other way round does work
|
||||
// {
|
||||
// doc: "non-matching ports (stored without port)",
|
||||
// storeURL: "https://foobar.example.com",
|
||||
// readURL: "https://foobar.example.com:5678",
|
||||
// },
|
||||
{
|
||||
doc: "non-matching paths",
|
||||
storeURL: "https://foobar.example.com:1234/one/two",
|
||||
readURL: "https://foobar.example.com:1234/five/six",
|
||||
},
|
||||
}
|
||||
|
||||
helper := Osxkeychain{}
|
||||
t.Cleanup(func() {
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Clean store before testing.
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err)
|
||||
}
|
||||
if _, _, err := helper.Get(tc.readURL); err == nil {
|
||||
t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", tc.storeURL, tc.readURL)
|
||||
}
|
||||
if err := helper.Delete(tc.storeURL); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSXKeychainHelperStoreRetrieve verifies that secrets stored in the
|
||||
// the keychain can be read back using the URL that was used to store them.
|
||||
func TestOSXKeychainHelperStoreRetrieve(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
}{
|
||||
{url: "foobar.example.com"},
|
||||
{url: "foobar.example.com:2376"},
|
||||
{url: "//foobar.example.com:2376"},
|
||||
{url: "https://foobar.example.com:2376"},
|
||||
{url: "http://foobar.example.com:2376"},
|
||||
{url: "https://foobar.example.com:2376/some/path"},
|
||||
{url: "https://foobar.example.com:2376/some/other/path"},
|
||||
{url: "https://foobar.example.com:2376/some/other/path?foo=bar"},
|
||||
}
|
||||
|
||||
helper := Osxkeychain{}
|
||||
t.Cleanup(func() {
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("cleanup: failed to delete '%s': %v", tc.url, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Clean store before testing.
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("prepare: failed to delete '%s': %v", tc.url, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we don't delete between individual tests here, to verify that
|
||||
// subsequent stores/overwrites don't affect storing / retrieving secrets.
|
||||
for i, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.url, func(t *testing.T) {
|
||||
c := &credentials.Credentials{
|
||||
ServerURL: tc.url,
|
||||
Username: fmt.Sprintf("user-%d", i),
|
||||
Secret: fmt.Sprintf("secret-%d", i),
|
||||
}
|
||||
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Fatalf("Error: failed to store secret for URL: %s: %s", tc.url, err)
|
||||
}
|
||||
user, secret, err := helper.Get(tc.url)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: failed to read secret for URL %q: %s", tc.url, err)
|
||||
}
|
||||
if user != c.Username {
|
||||
t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, tc.url)
|
||||
}
|
||||
if secret != c.Secret {
|
||||
t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, tc.url)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingCredentials(t *testing.T) {
|
||||
const nonExistingCred = "https://adsfasdf.invalid/asdfsdddd"
|
||||
helper := Osxkeychain{}
|
||||
_, _, err := helper.Get(nonExistingCred)
|
||||
if !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("expected ErrCredentialsNotFound, got %v", err)
|
||||
}
|
||||
err = helper.Delete(nonExistingCred)
|
||||
if !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("expected ErrCredentialsNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
+23
-15
@@ -9,10 +9,11 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -20,19 +21,20 @@ import (
|
||||
)
|
||||
|
||||
// PASS_FOLDER contains the directory where credentials are stored
|
||||
const PASS_FOLDER = "docker-credential-helpers" //nolint: golint
|
||||
const PASS_FOLDER = "docker-credential-helpers" //nolint:revive
|
||||
|
||||
// Pass handles secrets using Linux secret-service as a store.
|
||||
// Pass handles secrets using pass as a store.
|
||||
type Pass struct{}
|
||||
|
||||
// Ideally these would be stored as members of Pass, but since all of Pass's
|
||||
// methods have value receivers, not pointer receivers, and changing that is
|
||||
// backwards incompatible, we assume that all Pass instances share the same configuration
|
||||
|
||||
var (
|
||||
// initializationMutex is held while initializing so that only one 'pass'
|
||||
// round-tripping is done to check pass is functioning.
|
||||
var initializationMutex sync.Mutex
|
||||
var passInitialized bool
|
||||
initializationMutex sync.Mutex
|
||||
passInitialized bool
|
||||
)
|
||||
|
||||
// CheckInitialized checks whether the password helper can be used. It
|
||||
// internally caches and so may be safely called multiple times with no impact
|
||||
@@ -103,11 +105,11 @@ func (p Pass) Delete(serverURL string) error {
|
||||
}
|
||||
|
||||
func getPassDir() string {
|
||||
passDir := "$HOME/.password-store"
|
||||
if envDir := os.Getenv("PASSWORD_STORE_DIR"); envDir != "" {
|
||||
passDir = envDir
|
||||
if passDir := os.Getenv("PASSWORD_STORE_DIR"); passDir != "" {
|
||||
return passDir
|
||||
}
|
||||
return os.ExpandEnv(passDir)
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(home, ".password-store")
|
||||
}
|
||||
|
||||
// listPassDir lists all the contents of a directory in the password store.
|
||||
@@ -116,16 +118,22 @@ func getPassDir() string {
|
||||
func listPassDir(args ...string) ([]os.FileInfo, error) {
|
||||
passDir := getPassDir()
|
||||
p := path.Join(append([]string{passDir, PASS_FOLDER}, args...)...)
|
||||
contents, err := ioutil.ReadDir(p)
|
||||
entries, err := os.ReadDir(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return []os.FileInfo{}, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return contents, nil
|
||||
infos := make([]fs.FileInfo, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
// Get returns the username and secret to use for a given registry server URL.
|
||||
@@ -138,7 +146,7 @@ func (p Pass) Get(serverURL string) (string, string, error) {
|
||||
|
||||
if _, err := os.Stat(path.Join(getPassDir(), PASS_FOLDER, encoded)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", nil
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
return "", "", err
|
||||
|
||||
+82
-33
@@ -1,3 +1,5 @@
|
||||
//go:build !windows
|
||||
|
||||
package pass
|
||||
|
||||
import (
|
||||
@@ -8,68 +10,115 @@ import (
|
||||
)
|
||||
|
||||
func TestPassHelper(t *testing.T) {
|
||||
helper := Pass{}
|
||||
|
||||
creds := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.docker.io:2376/v1",
|
||||
ServerURL: "https://foobar.example.com:2376/v1",
|
||||
Username: "nothing",
|
||||
Secret: "isthebestmeshuggahalbum",
|
||||
}
|
||||
|
||||
helper.Add(creds)
|
||||
helper := Pass{}
|
||||
if err := helper.checkInitialized(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
creds.ServerURL = "https://foobar.docker.io:9999/v2"
|
||||
helper.Add(creds)
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
u, s, err := helper.Get(creds.ServerURL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if u != creds.Username {
|
||||
t.Errorf("invalid username %s", u)
|
||||
}
|
||||
if s != creds.Secret {
|
||||
t.Errorf("invalid secret: %s", s)
|
||||
}
|
||||
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, _, err := helper.Get(creds.ServerURL); !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("expected credentials not found, actual: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassHelperCheckInit(t *testing.T) {
|
||||
helper := Pass{}
|
||||
if v := helper.CheckInitialized(); !v {
|
||||
t.Errorf("expected true, actual: %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassHelperList(t *testing.T) {
|
||||
creds := []*credentials.Credentials{
|
||||
{
|
||||
ServerURL: "https://foobar.example.com:2376/v1",
|
||||
Username: "foo",
|
||||
Secret: "isthebestmeshuggahalbum",
|
||||
},
|
||||
{
|
||||
ServerURL: "https://foobar.example.com:2375/v1",
|
||||
Username: "bar",
|
||||
Secret: "isthebestmeshuggahalbum",
|
||||
},
|
||||
}
|
||||
|
||||
helper := Pass{}
|
||||
if err := helper.checkInitialized(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, cred := range creds {
|
||||
if err := helper.Add(cred); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
credsList, err := helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for server, username := range credsList {
|
||||
if !(strings.Contains(server, "2376") ||
|
||||
strings.Contains(server, "9999")) {
|
||||
t.Fatalf("invalid url: %s", creds.ServerURL)
|
||||
if !(strings.HasSuffix(server, "2376/v1") || strings.HasSuffix(server, "2375/v1")) {
|
||||
t.Errorf("invalid url: %s", server)
|
||||
}
|
||||
|
||||
if username != "nothing" {
|
||||
t.Fatalf("invalid username: %v", username)
|
||||
if !(username == "foo" || username == "bar") {
|
||||
t.Errorf("invalid username: %v", username)
|
||||
}
|
||||
|
||||
u, s, err := helper.Get(server)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if u != username {
|
||||
t.Fatalf("invalid username %s", u)
|
||||
t.Errorf("invalid username %s", u)
|
||||
}
|
||||
|
||||
if s != "isthebestmeshuggahalbum" {
|
||||
t.Fatalf("invalid secret: %s", s)
|
||||
t.Errorf("invalid secret: %s", s)
|
||||
}
|
||||
|
||||
err = helper.Delete(server)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if err := helper.Delete(server); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
username, _, err = helper.Get(server)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if username != "" {
|
||||
t.Fatalf("%s shouldn't exist any more", username)
|
||||
if _, _, err := helper.Get(server); !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("expected credentials not found, actual: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
credsList, err = helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
}
|
||||
if len(credsList) != 0 {
|
||||
t.Error("didn't delete all creds?")
|
||||
}
|
||||
}
|
||||
|
||||
if len(credsList) != 0 {
|
||||
t.Fatal("didn't delete all creds?")
|
||||
func TestMissingCred(t *testing.T) {
|
||||
helper := Pass{}
|
||||
if _, _, err := helper.Get("garbage"); !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("expected credentials not found, actual: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -28,10 +28,24 @@ func Parse(registryURL string) (*url.URL, error) {
|
||||
return nil, errors.New("unsupported scheme: " + u.Scheme)
|
||||
}
|
||||
|
||||
if GetHostname(u) == "" {
|
||||
if u.Hostname() == "" {
|
||||
return nil, errors.New("no hostname in URL")
|
||||
}
|
||||
|
||||
u.RawQuery = ""
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// GetHostname returns the hostname of the URL
|
||||
//
|
||||
// Deprecated: use url.Hostname()
|
||||
func GetHostname(u *url.URL) string {
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
// GetPort returns the port number of the URL
|
||||
//
|
||||
// Deprecated: use url.Port()
|
||||
func GetPort(u *url.URL) string {
|
||||
return u.Port()
|
||||
}
|
||||
|
||||
+50
-23
@@ -13,34 +13,61 @@ func TestHelperParseURL(t *testing.T) {
|
||||
expectedURL string
|
||||
err error
|
||||
}{
|
||||
{url: "foobar.docker.io", expectedURL: "//foobar.docker.io"},
|
||||
{url: "foobar.docker.io:2376", expectedURL: "//foobar.docker.io:2376"},
|
||||
{url: "//foobar.docker.io:2376", expectedURL: "//foobar.docker.io:2376"},
|
||||
{url: "http://foobar.docker.io:2376", expectedURL: "http://foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376", expectedURL: "https://foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376/some/path", expectedURL: "https://foobar.docker.io:2376/some/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar", expectedURL: "https://foobar.docker.io:2376/some/other/path"},
|
||||
{url: "/foobar.docker.io", err: errors.New("no hostname in URL")},
|
||||
{url: "ftp://foobar.docker.io:2376", err: errors.New("unsupported scheme: ftp")},
|
||||
{
|
||||
url: "foobar.example.com",
|
||||
expectedURL: "//foobar.example.com",
|
||||
},
|
||||
{
|
||||
url: "foobar.example.com:2376",
|
||||
expectedURL: "//foobar.example.com:2376",
|
||||
},
|
||||
{
|
||||
url: "//foobar.example.com:2376",
|
||||
expectedURL: "//foobar.example.com:2376",
|
||||
},
|
||||
{
|
||||
url: "http://foobar.example.com:2376",
|
||||
expectedURL: "http://foobar.example.com:2376",
|
||||
},
|
||||
{
|
||||
url: "https://foobar.example.com:2376",
|
||||
expectedURL: "https://foobar.example.com:2376",
|
||||
},
|
||||
{
|
||||
url: "https://foobar.example.com:2376/some/path",
|
||||
expectedURL: "https://foobar.example.com:2376/some/path",
|
||||
},
|
||||
{
|
||||
url: "https://foobar.example.com:2376/some/other/path?foo=bar",
|
||||
expectedURL: "https://foobar.example.com:2376/some/other/path",
|
||||
},
|
||||
{
|
||||
url: "/foobar.example.com",
|
||||
err: errors.New("no hostname in URL"),
|
||||
},
|
||||
{
|
||||
url: "ftp://foobar.example.com:2376",
|
||||
err: errors.New("unsupported scheme: ftp"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
u, err := Parse(te.url)
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.url, func(t *testing.T) {
|
||||
u, err := Parse(tc.url)
|
||||
|
||||
if te.err == nil && err != nil {
|
||||
t.Errorf("Error: failed to parse URL %q: %s", te.url, err)
|
||||
continue
|
||||
if tc.err == nil && err != nil {
|
||||
t.Fatalf("Error: failed to parse URL %q: %s", tc.url, err)
|
||||
}
|
||||
if te.err != nil && err == nil {
|
||||
t.Errorf("Error: expected error %q, got none when parsing URL %q", te.err, te.url)
|
||||
continue
|
||||
if tc.err != nil && err == nil {
|
||||
t.Fatalf("Error: expected error %q, got none when parsing URL %q", tc.err, tc.url)
|
||||
}
|
||||
if te.err != nil && err.Error() != te.err.Error() {
|
||||
t.Errorf("Error: expected error %q, got %q when parsing URL %q", te.err, err, te.url)
|
||||
continue
|
||||
}
|
||||
if u != nil && u.String() != te.expectedURL {
|
||||
t.Errorf("Error: expected URL: %q, but got %q for URL: %q", te.expectedURL, u.String(), te.url)
|
||||
if tc.err != nil && err.Error() != tc.err.Error() {
|
||||
t.Fatalf("Error: expected error %q, got %q when parsing URL %q", tc.err, err, tc.url)
|
||||
}
|
||||
if u != nil && u.String() != tc.expectedURL {
|
||||
t.Errorf("Error: expected URL: %q, but got %q for URL: %q", tc.expectedURL, u.String(), tc.url)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//+build go1.8
|
||||
|
||||
package registryurl
|
||||
|
||||
import (
|
||||
url "net/url"
|
||||
)
|
||||
|
||||
// GetHostname returns the hostname of the URL
|
||||
func GetHostname(u *url.URL) string {
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
// GetPort returns the port number of the URL
|
||||
func GetPort(u *url.URL) string {
|
||||
return u.Port()
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
//+build !go1.8
|
||||
|
||||
package registryurl
|
||||
|
||||
import (
|
||||
url "net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetHostname(u *url.URL) string {
|
||||
return stripPort(u.Host)
|
||||
}
|
||||
|
||||
func GetPort(u *url.URL) string {
|
||||
return portOnly(u.Host)
|
||||
}
|
||||
|
||||
func stripPort(hostport string) string {
|
||||
colon := strings.IndexByte(hostport, ':')
|
||||
if colon == -1 {
|
||||
return hostport
|
||||
}
|
||||
if i := strings.IndexByte(hostport, ']'); i != -1 {
|
||||
return strings.TrimPrefix(hostport[:i], "[")
|
||||
}
|
||||
return hostport[:colon]
|
||||
}
|
||||
|
||||
func portOnly(hostport string) string {
|
||||
colon := strings.IndexByte(hostport, ':')
|
||||
if colon == -1 {
|
||||
return ""
|
||||
}
|
||||
if i := strings.Index(hostport, "]:"); i != -1 {
|
||||
return hostport[i+len("]:"):]
|
||||
}
|
||||
if strings.Contains(hostport, "]") {
|
||||
return ""
|
||||
}
|
||||
return hostport[colon+len(":"):]
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build linux && cgo
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "secretservice_linux.h"
|
||||
#include "secretservice.h"
|
||||
|
||||
const SecretSchema *docker_get_schema(void)
|
||||
{
|
||||
@@ -1,12 +1,15 @@
|
||||
//go:build linux && cgo
|
||||
|
||||
package secretservice
|
||||
|
||||
/*
|
||||
#cgo pkg-config: libsecret-1
|
||||
|
||||
#include "secretservice_linux.h"
|
||||
#include "secretservice.h"
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
@@ -96,7 +99,8 @@ func (h Secretservice) List() (map[string]string, error) {
|
||||
defer C.freeListData(&acctsC, listLenC)
|
||||
if err != nil {
|
||||
defer C.g_error_free(err)
|
||||
return nil, errors.New("Error from list function in secretservice_linux.c likely due to error in secretservice library")
|
||||
errMsg := (*C.char)(unsafe.Pointer(err.message))
|
||||
return nil, errors.New(C.GoString(errMsg))
|
||||
}
|
||||
|
||||
resp := make(map[string]string)
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build linux && cgo
|
||||
|
||||
package secretservice
|
||||
|
||||
import (
|
||||
@@ -11,7 +13,7 @@ func TestSecretServiceHelper(t *testing.T) {
|
||||
t.Skip("test requires gnome-keyring but travis CI doesn't have it")
|
||||
|
||||
creds := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.docker.io:2376/v1",
|
||||
ServerURL: "https://foobar.example.com:2376/v1",
|
||||
Username: "foobar",
|
||||
Secret: "foobarbaz",
|
||||
}
|
||||
@@ -28,7 +30,6 @@ func TestSecretServiceHelper(t *testing.T) {
|
||||
// remove them as they probably come from a previous failed test
|
||||
for k, v := range oldAuths {
|
||||
if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 {
|
||||
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
+2
@@ -21,3 +21,5 @@ _testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
|
||||
coverage.txt
|
||||
|
||||
+50
-3
@@ -3,9 +3,11 @@ wincred
|
||||
|
||||
Go wrapper around the Windows Credential Manager API functions.
|
||||
|
||||

|
||||
[](https://godoc.org/github.com/danieljoos/wincred)
|
||||
|
||||
[](https://github.com/danieljoos/wincred/releases/latest)
|
||||
[](https://github.com/danieljoos/wincred/actions?query=workflow%3Atest)
|
||||
[](https://goreportcard.com/report/github.com/danieljoos/wincred)
|
||||
[](https://codecov.io/gh/danieljoos/wincred)
|
||||
[](https://pkg.go.dev/github.com/danieljoos/wincred)
|
||||
|
||||
Installation
|
||||
------------
|
||||
@@ -96,3 +98,48 @@ func main() {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Hints
|
||||
-----
|
||||
|
||||
### Encoding
|
||||
|
||||
The credential objects simply store byte arrays without specific meaning or encoding.
|
||||
For sharing between different applications, it might make sense to apply an explicit string encoding - for example **UTF-16 LE** (used nearly everywhere in the Win32 API).
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/danieljoos/wincred"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cred := wincred.NewGenericCredential("myGoApplication")
|
||||
|
||||
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
|
||||
blob, _, err := transform.Bytes(encoder, []byte("mysecret"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cred.CredentialBlob = blob
|
||||
err = cred.Write()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Limitations
|
||||
|
||||
The size of a credential blob is limited to **2560 Bytes** by the Windows API.
|
||||
|
||||
+12
-27
@@ -5,30 +5,11 @@ package wincred
|
||||
import (
|
||||
"encoding/binary"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// uf16PtrToString creates a Go string from a pointer to a UTF16 encoded zero-terminated string.
|
||||
// Such pointers are returned from the Windows API calls.
|
||||
// The function creates a copy of the string.
|
||||
func utf16PtrToString(wstr *uint16) string {
|
||||
if wstr != nil {
|
||||
for len := 0; ; len++ {
|
||||
ptr := unsafe.Pointer(uintptr(unsafe.Pointer(wstr)) + uintptr(len)*unsafe.Sizeof(*wstr)) // see https://golang.org/pkg/unsafe/#Pointer (3)
|
||||
if *(*uint16)(ptr) == 0 {
|
||||
return string(utf16.Decode(*(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: uintptr(unsafe.Pointer(wstr)),
|
||||
Len: len,
|
||||
Cap: len,
|
||||
}))))
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
syscall "golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// utf16ToByte creates a byte array from a given UTF 16 char array.
|
||||
func utf16ToByte(wstr []uint16) (result []byte) {
|
||||
@@ -41,7 +22,11 @@ func utf16ToByte(wstr []uint16) (result []byte) {
|
||||
|
||||
// utf16FromString creates a UTF16 char array from a string.
|
||||
func utf16FromString(str string) []uint16 {
|
||||
return syscall.StringToUTF16(str)
|
||||
res, err := syscall.UTF16FromString(str)
|
||||
if err != nil {
|
||||
return []uint16{}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// goBytes copies the given C byte array to a Go byte array (see `C.GoBytes`).
|
||||
@@ -65,10 +50,10 @@ func sysToCredential(cred *sysCREDENTIAL) (result *Credential) {
|
||||
return nil
|
||||
}
|
||||
result = new(Credential)
|
||||
result.Comment = utf16PtrToString(cred.Comment)
|
||||
result.TargetName = utf16PtrToString(cred.TargetName)
|
||||
result.TargetAlias = utf16PtrToString(cred.TargetAlias)
|
||||
result.UserName = utf16PtrToString(cred.UserName)
|
||||
result.Comment = syscall.UTF16PtrToString(cred.Comment)
|
||||
result.TargetName = syscall.UTF16PtrToString(cred.TargetName)
|
||||
result.TargetAlias = syscall.UTF16PtrToString(cred.TargetAlias)
|
||||
result.UserName = syscall.UTF16PtrToString(cred.UserName)
|
||||
result.LastWritten = time.Unix(0, cred.LastWritten.Nanoseconds())
|
||||
result.Persist = CredentialPersistence(cred.Persist)
|
||||
result.CredentialBlob = goBytes(cred.CredentialBlob, cred.CredentialBlobSize)
|
||||
@@ -80,7 +65,7 @@ func sysToCredential(cred *sysCREDENTIAL) (result *Credential) {
|
||||
}))
|
||||
for i, attr := range attrSlice {
|
||||
resultAttr := &result.Attributes[i]
|
||||
resultAttr.Keyword = utf16PtrToString(attr.Keyword)
|
||||
resultAttr.Keyword = syscall.UTF16PtrToString(attr.Keyword)
|
||||
resultAttr.Value = goBytes(attr.Value, attr.ValueSize)
|
||||
}
|
||||
return result
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
module github.com/danieljoos/wincred
|
||||
|
||||
go 1.13
|
||||
|
||||
require github.com/stretchr/testify v1.5.1
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
+17
-12
@@ -1,3 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package wincred
|
||||
@@ -6,16 +7,17 @@ import (
|
||||
"reflect"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
|
||||
|
||||
procCredRead proc = modadvapi32.NewProc("CredReadW")
|
||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||
procCredRead = modadvapi32.NewProc("CredReadW")
|
||||
procCredWrite proc = modadvapi32.NewProc("CredWriteW")
|
||||
procCredDelete proc = modadvapi32.NewProc("CredDeleteW")
|
||||
procCredFree proc = modadvapi32.NewProc("CredFree")
|
||||
procCredEnumerate proc = modadvapi32.NewProc("CredEnumerateW")
|
||||
procCredEnumerate = modadvapi32.NewProc("CredEnumerateW")
|
||||
)
|
||||
|
||||
// Interface for syscall.Proc: helps testing
|
||||
@@ -29,7 +31,7 @@ type sysCREDENTIAL struct {
|
||||
Type uint32
|
||||
TargetName *uint16
|
||||
Comment *uint16
|
||||
LastWritten syscall.Filetime
|
||||
LastWritten windows.Filetime
|
||||
CredentialBlobSize uint32
|
||||
CredentialBlob uintptr
|
||||
Persist uint32
|
||||
@@ -59,15 +61,17 @@ const (
|
||||
sysCRED_TYPE_DOMAIN_EXTENDED sysCRED_TYPE = 0x6
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes
|
||||
sysERROR_NOT_FOUND = syscall.Errno(1168)
|
||||
sysERROR_INVALID_PARAMETER = syscall.Errno(87)
|
||||
sysERROR_NOT_FOUND = windows.Errno(1168)
|
||||
sysERROR_INVALID_PARAMETER = windows.Errno(87)
|
||||
sysERROR_BAD_USERNAME = windows.Errno(2202)
|
||||
)
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credreadw
|
||||
func sysCredRead(targetName string, typ sysCRED_TYPE) (*Credential, error) {
|
||||
var pcred *sysCREDENTIAL
|
||||
targetNamePtr, _ := syscall.UTF16PtrFromString(targetName)
|
||||
ret, _, err := procCredRead.Call(
|
||||
targetNamePtr, _ := windows.UTF16PtrFromString(targetName)
|
||||
ret, _, err := syscall.SyscallN(
|
||||
procCredRead.Addr(),
|
||||
uintptr(unsafe.Pointer(targetNamePtr)),
|
||||
uintptr(typ),
|
||||
0,
|
||||
@@ -98,7 +102,7 @@ func sysCredWrite(cred *Credential, typ sysCRED_TYPE) error {
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creddeletew
|
||||
func sysCredDelete(cred *Credential, typ sysCRED_TYPE) error {
|
||||
targetNamePtr, _ := syscall.UTF16PtrFromString(cred.TargetName)
|
||||
targetNamePtr, _ := windows.UTF16PtrFromString(cred.TargetName)
|
||||
ret, _, err := procCredDelete.Call(
|
||||
uintptr(unsafe.Pointer(targetNamePtr)),
|
||||
uintptr(typ),
|
||||
@@ -117,9 +121,10 @@ func sysCredEnumerate(filter string, all bool) ([]*Credential, error) {
|
||||
var pcreds uintptr
|
||||
var filterPtr *uint16
|
||||
if !all {
|
||||
filterPtr, _ = syscall.UTF16PtrFromString(filter)
|
||||
filterPtr, _ = windows.UTF16PtrFromString(filter)
|
||||
}
|
||||
ret, _, err := procCredEnumerate.Call(
|
||||
ret, _, err := syscall.SyscallN(
|
||||
procCredEnumerate.Addr(),
|
||||
uintptr(unsafe.Pointer(filterPtr)),
|
||||
0,
|
||||
uintptr(unsafe.Pointer(&count)),
|
||||
|
||||
+5
-2
@@ -16,6 +16,9 @@ const (
|
||||
// This error constant can be used to check if the given function parameters were invalid.
|
||||
// For example when trying to create a new generic credential with an empty target name.
|
||||
ErrInvalidParameter = sysERROR_INVALID_PARAMETER
|
||||
|
||||
// ErrBadUsername is returned when the credential's username is invalid.
|
||||
ErrBadUsername = sysERROR_BAD_USERNAME
|
||||
)
|
||||
|
||||
// GetGenericCredential fetches the generic credential with the given name from Windows credential manager.
|
||||
@@ -23,7 +26,7 @@ const (
|
||||
func GetGenericCredential(targetName string) (*GenericCredential, error) {
|
||||
cred, err := sysCredRead(targetName, sysCRED_TYPE_GENERIC)
|
||||
if cred != nil {
|
||||
return &GenericCredential{*cred}, err
|
||||
return &GenericCredential{Credential: *cred}, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -55,7 +58,7 @@ func (t *GenericCredential) Delete() (err error) {
|
||||
func GetDomainPassword(targetName string) (*DomainPassword, error) {
|
||||
cred, err := sysCredRead(targetName, sysCRED_TYPE_DOMAIN_PASSWORD)
|
||||
if cred != nil {
|
||||
return &DomainPassword{*cred}, err
|
||||
return &DomainPassword{Credential: *cred}, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package execabs is a drop-in replacement for os/exec
|
||||
// that requires PATH lookups to find absolute paths.
|
||||
// That is, execabs.Command("cmd") runs the same PATH lookup
|
||||
// as exec.Command("cmd"), but if the result is a path
|
||||
// which is relative, the Run and Start methods will report
|
||||
// an error instead of running the executable.
|
||||
//
|
||||
// See https://blog.golang.org/path-security for more information
|
||||
// about when it may be necessary or appropriate to use this package.
|
||||
package execabs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ErrNotFound is the error resulting if a path search failed to find an executable file.
|
||||
// It is an alias for exec.ErrNotFound.
|
||||
var ErrNotFound = exec.ErrNotFound
|
||||
|
||||
// Cmd represents an external command being prepared or run.
|
||||
// It is an alias for exec.Cmd.
|
||||
type Cmd = exec.Cmd
|
||||
|
||||
// Error is returned by LookPath when it fails to classify a file as an executable.
|
||||
// It is an alias for exec.Error.
|
||||
type Error = exec.Error
|
||||
|
||||
// An ExitError reports an unsuccessful exit by a command.
|
||||
// It is an alias for exec.ExitError.
|
||||
type ExitError = exec.ExitError
|
||||
|
||||
func relError(file, path string) error {
|
||||
return fmt.Errorf("%s resolves to executable in current directory (.%c%s)", file, filepath.Separator, path)
|
||||
}
|
||||
|
||||
// LookPath searches for an executable named file in the directories
|
||||
// named by the PATH environment variable. If file contains a slash,
|
||||
// it is tried directly and the PATH is not consulted. The result will be
|
||||
// an absolute path.
|
||||
//
|
||||
// LookPath differs from exec.LookPath in its handling of PATH lookups,
|
||||
// which are used for file names without slashes. If exec.LookPath's
|
||||
// PATH lookup would have returned an executable from the current directory,
|
||||
// LookPath instead returns an error.
|
||||
func LookPath(file string) (string, error) {
|
||||
path, err := exec.LookPath(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if filepath.Base(file) == file && !filepath.IsAbs(path) {
|
||||
return "", relError(file, path)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func fixCmd(name string, cmd *exec.Cmd) {
|
||||
if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) {
|
||||
// exec.Command was called with a bare binary name and
|
||||
// exec.LookPath returned a path which is not absolute.
|
||||
// Set cmd.lookPathErr and clear cmd.Path so that it
|
||||
// cannot be run.
|
||||
lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer()))
|
||||
if *lookPathErr == nil {
|
||||
*lookPathErr = relError(name, cmd.Path)
|
||||
}
|
||||
cmd.Path = ""
|
||||
}
|
||||
}
|
||||
|
||||
// CommandContext is like Command but includes a context.
|
||||
//
|
||||
// The provided context is used to kill the process (by calling os.Process.Kill)
|
||||
// if the context becomes done before the command completes on its own.
|
||||
func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
|
||||
cmd := exec.CommandContext(ctx, name, arg...)
|
||||
fixCmd(name, cmd)
|
||||
return cmd
|
||||
|
||||
}
|
||||
|
||||
// Command returns the Cmd struct to execute the named program with the given arguments.
|
||||
// See exec.Command for most details.
|
||||
//
|
||||
// Command differs from exec.Command in its handling of PATH lookups,
|
||||
// which are used when the program name contains no slashes.
|
||||
// If exec.Command would have returned an exec.Cmd configured to run an
|
||||
// executable from the current directory, Command instead
|
||||
// returns an exec.Cmd that will return an error from Start or Run.
|
||||
func Command(name string, arg ...string) *exec.Cmd {
|
||||
cmd := exec.Command(name, arg...)
|
||||
fixCmd(name, cmd)
|
||||
return cmd
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && go1.9
|
||||
|
||||
package windows
|
||||
|
||||
import "syscall"
|
||||
|
||||
type Errno = syscall.Errno
|
||||
type SysProcAttr = syscall.SysProcAttr
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// We need to use LoadLibrary and GetProcAddress from the Go runtime, because
|
||||
// the these symbols are loaded by the system linker and are required to
|
||||
// dynamically load additional symbols. Note that in the Go runtime, these
|
||||
// return syscall.Handle and syscall.Errno, but these are the same, in fact,
|
||||
// as windows.Handle and windows.Errno, and we intend to keep these the same.
|
||||
|
||||
//go:linkname syscall_loadlibrary syscall.loadlibrary
|
||||
func syscall_loadlibrary(filename *uint16) (handle Handle, err Errno)
|
||||
|
||||
//go:linkname syscall_getprocaddress syscall.getprocaddress
|
||||
func syscall_getprocaddress(handle Handle, procname *uint8) (proc uintptr, err Errno)
|
||||
|
||||
// DLLError describes reasons for DLL load failures.
|
||||
type DLLError struct {
|
||||
Err error
|
||||
ObjName string
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e *DLLError) Error() string { return e.Msg }
|
||||
|
||||
func (e *DLLError) Unwrap() error { return e.Err }
|
||||
|
||||
// A DLL implements access to a single DLL.
|
||||
type DLL struct {
|
||||
Name string
|
||||
Handle Handle
|
||||
}
|
||||
|
||||
// LoadDLL loads DLL file into memory.
|
||||
//
|
||||
// Warning: using LoadDLL without an absolute path name is subject to
|
||||
// DLL preloading attacks. To safely load a system DLL, use LazyDLL
|
||||
// with System set to true, or use LoadLibraryEx directly.
|
||||
func LoadDLL(name string) (dll *DLL, err error) {
|
||||
namep, err := UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, e := syscall_loadlibrary(namep)
|
||||
if e != 0 {
|
||||
return nil, &DLLError{
|
||||
Err: e,
|
||||
ObjName: name,
|
||||
Msg: "Failed to load " + name + ": " + e.Error(),
|
||||
}
|
||||
}
|
||||
d := &DLL{
|
||||
Name: name,
|
||||
Handle: h,
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// MustLoadDLL is like LoadDLL but panics if load operation failes.
|
||||
func MustLoadDLL(name string) *DLL {
|
||||
d, e := LoadDLL(name)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// FindProc searches DLL d for procedure named name and returns *Proc
|
||||
// if found. It returns an error if search fails.
|
||||
func (d *DLL) FindProc(name string) (proc *Proc, err error) {
|
||||
namep, err := BytePtrFromString(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, e := syscall_getprocaddress(d.Handle, namep)
|
||||
if e != 0 {
|
||||
return nil, &DLLError{
|
||||
Err: e,
|
||||
ObjName: name,
|
||||
Msg: "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(),
|
||||
}
|
||||
}
|
||||
p := &Proc{
|
||||
Dll: d,
|
||||
Name: name,
|
||||
addr: a,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// MustFindProc is like FindProc but panics if search fails.
|
||||
func (d *DLL) MustFindProc(name string) *Proc {
|
||||
p, e := d.FindProc(name)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// FindProcByOrdinal searches DLL d for procedure by ordinal and returns *Proc
|
||||
// if found. It returns an error if search fails.
|
||||
func (d *DLL) FindProcByOrdinal(ordinal uintptr) (proc *Proc, err error) {
|
||||
a, e := GetProcAddressByOrdinal(d.Handle, ordinal)
|
||||
name := "#" + itoa(int(ordinal))
|
||||
if e != nil {
|
||||
return nil, &DLLError{
|
||||
Err: e,
|
||||
ObjName: name,
|
||||
Msg: "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(),
|
||||
}
|
||||
}
|
||||
p := &Proc{
|
||||
Dll: d,
|
||||
Name: name,
|
||||
addr: a,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// MustFindProcByOrdinal is like FindProcByOrdinal but panics if search fails.
|
||||
func (d *DLL) MustFindProcByOrdinal(ordinal uintptr) *Proc {
|
||||
p, e := d.FindProcByOrdinal(ordinal)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Release unloads DLL d from memory.
|
||||
func (d *DLL) Release() (err error) {
|
||||
return FreeLibrary(d.Handle)
|
||||
}
|
||||
|
||||
// A Proc implements access to a procedure inside a DLL.
|
||||
type Proc struct {
|
||||
Dll *DLL
|
||||
Name string
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
// Addr returns the address of the procedure represented by p.
|
||||
// The return value can be passed to Syscall to run the procedure.
|
||||
func (p *Proc) Addr() uintptr {
|
||||
return p.addr
|
||||
}
|
||||
|
||||
//go:uintptrescapes
|
||||
|
||||
// Call executes procedure p with arguments a. It will panic, if more than 15 arguments
|
||||
// are supplied.
|
||||
//
|
||||
// The returned error is always non-nil, constructed from the result of GetLastError.
|
||||
// Callers must inspect the primary return value to decide whether an error occurred
|
||||
// (according to the semantics of the specific function being called) before consulting
|
||||
// the error. The error will be guaranteed to contain windows.Errno.
|
||||
func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
|
||||
switch len(a) {
|
||||
case 0:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), 0, 0, 0)
|
||||
case 1:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], 0, 0)
|
||||
case 2:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], 0)
|
||||
case 3:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], a[2])
|
||||
case 4:
|
||||
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], 0, 0)
|
||||
case 5:
|
||||
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], 0)
|
||||
case 6:
|
||||
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5])
|
||||
case 7:
|
||||
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0, 0)
|
||||
case 8:
|
||||
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 0)
|
||||
case 9:
|
||||
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8])
|
||||
case 10:
|
||||
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], 0, 0)
|
||||
case 11:
|
||||
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0)
|
||||
case 12:
|
||||
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11])
|
||||
case 13:
|
||||
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0)
|
||||
case 14:
|
||||
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0)
|
||||
case 15:
|
||||
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14])
|
||||
default:
|
||||
panic("Call " + p.Name + " with too many arguments " + itoa(len(a)) + ".")
|
||||
}
|
||||
}
|
||||
|
||||
// A LazyDLL implements access to a single DLL.
|
||||
// It will delay the load of the DLL until the first
|
||||
// call to its Handle method or to one of its
|
||||
// LazyProc's Addr method.
|
||||
type LazyDLL struct {
|
||||
Name string
|
||||
|
||||
// System determines whether the DLL must be loaded from the
|
||||
// Windows System directory, bypassing the normal DLL search
|
||||
// path.
|
||||
System bool
|
||||
|
||||
mu sync.Mutex
|
||||
dll *DLL // non nil once DLL is loaded
|
||||
}
|
||||
|
||||
// Load loads DLL file d.Name into memory. It returns an error if fails.
|
||||
// Load will not try to load DLL, if it is already loaded into memory.
|
||||
func (d *LazyDLL) Load() error {
|
||||
// Non-racy version of:
|
||||
// if d.dll != nil {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll))) != nil {
|
||||
return nil
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.dll != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// kernel32.dll is special, since it's where LoadLibraryEx comes from.
|
||||
// The kernel already special-cases its name, so it's always
|
||||
// loaded from system32.
|
||||
var dll *DLL
|
||||
var err error
|
||||
if d.Name == "kernel32.dll" {
|
||||
dll, err = LoadDLL(d.Name)
|
||||
} else {
|
||||
dll, err = loadLibraryEx(d.Name, d.System)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Non-racy version of:
|
||||
// d.dll = dll
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll)), unsafe.Pointer(dll))
|
||||
return nil
|
||||
}
|
||||
|
||||
// mustLoad is like Load but panics if search fails.
|
||||
func (d *LazyDLL) mustLoad() {
|
||||
e := d.Load()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle returns d's module handle.
|
||||
func (d *LazyDLL) Handle() uintptr {
|
||||
d.mustLoad()
|
||||
return uintptr(d.dll.Handle)
|
||||
}
|
||||
|
||||
// NewProc returns a LazyProc for accessing the named procedure in the DLL d.
|
||||
func (d *LazyDLL) NewProc(name string) *LazyProc {
|
||||
return &LazyProc{l: d, Name: name}
|
||||
}
|
||||
|
||||
// NewLazyDLL creates new LazyDLL associated with DLL file.
|
||||
func NewLazyDLL(name string) *LazyDLL {
|
||||
return &LazyDLL{Name: name}
|
||||
}
|
||||
|
||||
// NewLazySystemDLL is like NewLazyDLL, but will only
|
||||
// search Windows System directory for the DLL if name is
|
||||
// a base name (like "advapi32.dll").
|
||||
func NewLazySystemDLL(name string) *LazyDLL {
|
||||
return &LazyDLL{Name: name, System: true}
|
||||
}
|
||||
|
||||
// A LazyProc implements access to a procedure inside a LazyDLL.
|
||||
// It delays the lookup until the Addr method is called.
|
||||
type LazyProc struct {
|
||||
Name string
|
||||
|
||||
mu sync.Mutex
|
||||
l *LazyDLL
|
||||
proc *Proc
|
||||
}
|
||||
|
||||
// Find searches DLL for procedure named p.Name. It returns
|
||||
// an error if search fails. Find will not search procedure,
|
||||
// if it is already found and loaded into memory.
|
||||
func (p *LazyProc) Find() error {
|
||||
// Non-racy version of:
|
||||
// if p.proc == nil {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc))) == nil {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.proc == nil {
|
||||
e := p.l.Load()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
proc, e := p.l.dll.FindProc(p.Name)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
// Non-racy version of:
|
||||
// p.proc = proc
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc)), unsafe.Pointer(proc))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mustFind is like Find but panics if search fails.
|
||||
func (p *LazyProc) mustFind() {
|
||||
e := p.Find()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Addr returns the address of the procedure represented by p.
|
||||
// The return value can be passed to Syscall to run the procedure.
|
||||
// It will panic if the procedure cannot be found.
|
||||
func (p *LazyProc) Addr() uintptr {
|
||||
p.mustFind()
|
||||
return p.proc.Addr()
|
||||
}
|
||||
|
||||
//go:uintptrescapes
|
||||
|
||||
// Call executes procedure p with arguments a. It will panic, if more than 15 arguments
|
||||
// are supplied. It will also panic if the procedure cannot be found.
|
||||
//
|
||||
// The returned error is always non-nil, constructed from the result of GetLastError.
|
||||
// Callers must inspect the primary return value to decide whether an error occurred
|
||||
// (according to the semantics of the specific function being called) before consulting
|
||||
// the error. The error will be guaranteed to contain windows.Errno.
|
||||
func (p *LazyProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
|
||||
p.mustFind()
|
||||
return p.proc.Call(a...)
|
||||
}
|
||||
|
||||
var canDoSearchSystem32Once struct {
|
||||
sync.Once
|
||||
v bool
|
||||
}
|
||||
|
||||
func initCanDoSearchSystem32() {
|
||||
// https://msdn.microsoft.com/en-us/library/ms684179(v=vs.85).aspx says:
|
||||
// "Windows 7, Windows Server 2008 R2, Windows Vista, and Windows
|
||||
// Server 2008: The LOAD_LIBRARY_SEARCH_* flags are available on
|
||||
// systems that have KB2533623 installed. To determine whether the
|
||||
// flags are available, use GetProcAddress to get the address of the
|
||||
// AddDllDirectory, RemoveDllDirectory, or SetDefaultDllDirectories
|
||||
// function. If GetProcAddress succeeds, the LOAD_LIBRARY_SEARCH_*
|
||||
// flags can be used with LoadLibraryEx."
|
||||
canDoSearchSystem32Once.v = (modkernel32.NewProc("AddDllDirectory").Find() == nil)
|
||||
}
|
||||
|
||||
func canDoSearchSystem32() bool {
|
||||
canDoSearchSystem32Once.Do(initCanDoSearchSystem32)
|
||||
return canDoSearchSystem32Once.v
|
||||
}
|
||||
|
||||
func isBaseName(name string) bool {
|
||||
for _, c := range name {
|
||||
if c == ':' || c == '/' || c == '\\' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// loadLibraryEx wraps the Windows LoadLibraryEx function.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684179(v=vs.85).aspx
|
||||
//
|
||||
// If name is not an absolute path, LoadLibraryEx searches for the DLL
|
||||
// in a variety of automatic locations unless constrained by flags.
|
||||
// See: https://msdn.microsoft.com/en-us/library/ff919712%28VS.85%29.aspx
|
||||
func loadLibraryEx(name string, system bool) (*DLL, error) {
|
||||
loadDLL := name
|
||||
var flags uintptr
|
||||
if system {
|
||||
if canDoSearchSystem32() {
|
||||
flags = LOAD_LIBRARY_SEARCH_SYSTEM32
|
||||
} else if isBaseName(name) {
|
||||
// WindowsXP or unpatched Windows machine
|
||||
// trying to load "foo.dll" out of the system
|
||||
// folder, but LoadLibraryEx doesn't support
|
||||
// that yet on their system, so emulate it.
|
||||
systemdir, err := GetSystemDirectory()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loadDLL = systemdir + "\\" + name
|
||||
}
|
||||
}
|
||||
h, err := LoadLibraryEx(loadDLL, 0, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DLL{Name: name, Handle: h}, nil
|
||||
}
|
||||
|
||||
type errString string
|
||||
|
||||
func (s errString) Error() string { return string(s) }
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.12
|
||||
|
||||
// This file is here to allow bodyless functions with go:linkname for Go 1.11
|
||||
// and earlier (see https://golang.org/issue/23311).
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Windows environment variables.
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Getenv(key string) (value string, found bool) {
|
||||
return syscall.Getenv(key)
|
||||
}
|
||||
|
||||
func Setenv(key, value string) error {
|
||||
return syscall.Setenv(key, value)
|
||||
}
|
||||
|
||||
func Clearenv() {
|
||||
syscall.Clearenv()
|
||||
}
|
||||
|
||||
func Environ() []string {
|
||||
return syscall.Environ()
|
||||
}
|
||||
|
||||
// Returns a default environment associated with the token, rather than the current
|
||||
// process. If inheritExisting is true, then this environment also inherits the
|
||||
// environment of the current process.
|
||||
func (token Token) Environ(inheritExisting bool) (env []string, err error) {
|
||||
var block *uint16
|
||||
err = CreateEnvironmentBlock(&block, token, inheritExisting)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer DestroyEnvironmentBlock(block)
|
||||
blockp := unsafe.Pointer(block)
|
||||
for {
|
||||
entry := UTF16PtrToString((*uint16)(blockp))
|
||||
if len(entry) == 0 {
|
||||
break
|
||||
}
|
||||
env = append(env, entry)
|
||||
blockp = unsafe.Add(blockp, 2*(len(entry)+1))
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func Unsetenv(key string) error {
|
||||
return syscall.Unsetenv(key)
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
const (
|
||||
EVENTLOG_SUCCESS = 0
|
||||
EVENTLOG_ERROR_TYPE = 1
|
||||
EVENTLOG_WARNING_TYPE = 2
|
||||
EVENTLOG_INFORMATION_TYPE = 4
|
||||
EVENTLOG_AUDIT_SUCCESS = 8
|
||||
EVENTLOG_AUDIT_FAILURE = 16
|
||||
)
|
||||
|
||||
//sys RegisterEventSource(uncServerName *uint16, sourceName *uint16) (handle Handle, err error) [failretval==0] = advapi32.RegisterEventSourceW
|
||||
//sys DeregisterEventSource(handle Handle) (err error) = advapi32.DeregisterEventSource
|
||||
//sys ReportEvent(log Handle, etype uint16, category uint16, eventId uint32, usrSId uintptr, numStrings uint16, dataSize uint32, strings **uint16, rawData *byte) (err error) = advapi32.ReportEventW
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Fork, exec, wait, etc.
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
errorspkg "errors"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// EscapeArg rewrites command line argument s as prescribed
|
||||
// in http://msdn.microsoft.com/en-us/library/ms880421.
|
||||
// This function returns "" (2 double quotes) if s is empty.
|
||||
// Alternatively, these transformations are done:
|
||||
// - every back slash (\) is doubled, but only if immediately
|
||||
// followed by double quote (");
|
||||
// - every double quote (") is escaped by back slash (\);
|
||||
// - finally, s is wrapped with double quotes (arg -> "arg"),
|
||||
// but only if there is space or tab inside s.
|
||||
func EscapeArg(s string) string {
|
||||
if len(s) == 0 {
|
||||
return `""`
|
||||
}
|
||||
n := len(s)
|
||||
hasSpace := false
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '"', '\\':
|
||||
n++
|
||||
case ' ', '\t':
|
||||
hasSpace = true
|
||||
}
|
||||
}
|
||||
if hasSpace {
|
||||
n += 2 // Reserve space for quotes.
|
||||
}
|
||||
if n == len(s) {
|
||||
return s
|
||||
}
|
||||
|
||||
qs := make([]byte, n)
|
||||
j := 0
|
||||
if hasSpace {
|
||||
qs[j] = '"'
|
||||
j++
|
||||
}
|
||||
slashes := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
default:
|
||||
slashes = 0
|
||||
qs[j] = s[i]
|
||||
case '\\':
|
||||
slashes++
|
||||
qs[j] = s[i]
|
||||
case '"':
|
||||
for ; slashes > 0; slashes-- {
|
||||
qs[j] = '\\'
|
||||
j++
|
||||
}
|
||||
qs[j] = '\\'
|
||||
j++
|
||||
qs[j] = s[i]
|
||||
}
|
||||
j++
|
||||
}
|
||||
if hasSpace {
|
||||
for ; slashes > 0; slashes-- {
|
||||
qs[j] = '\\'
|
||||
j++
|
||||
}
|
||||
qs[j] = '"'
|
||||
j++
|
||||
}
|
||||
return string(qs[:j])
|
||||
}
|
||||
|
||||
// ComposeCommandLine escapes and joins the given arguments suitable for use as a Windows command line,
|
||||
// in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument,
|
||||
// or any program that uses CommandLineToArgv.
|
||||
func ComposeCommandLine(args []string) string {
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Per https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw:
|
||||
// “This function accepts command lines that contain a program name; the
|
||||
// program name can be enclosed in quotation marks or not.”
|
||||
//
|
||||
// Unfortunately, it provides no means of escaping interior quotation marks
|
||||
// within that program name, and we have no way to report them here.
|
||||
prog := args[0]
|
||||
mustQuote := len(prog) == 0
|
||||
for i := 0; i < len(prog); i++ {
|
||||
c := prog[i]
|
||||
if c <= ' ' || (c == '"' && i == 0) {
|
||||
// Force quotes for not only the ASCII space and tab as described in the
|
||||
// MSDN article, but also ASCII control characters.
|
||||
// The documentation for CommandLineToArgvW doesn't say what happens when
|
||||
// the first argument is not a valid program name, but it empirically
|
||||
// seems to drop unquoted control characters.
|
||||
mustQuote = true
|
||||
break
|
||||
}
|
||||
}
|
||||
var commandLine []byte
|
||||
if mustQuote {
|
||||
commandLine = make([]byte, 0, len(prog)+2)
|
||||
commandLine = append(commandLine, '"')
|
||||
for i := 0; i < len(prog); i++ {
|
||||
c := prog[i]
|
||||
if c == '"' {
|
||||
// This quote would interfere with our surrounding quotes.
|
||||
// We have no way to report an error, so just strip out
|
||||
// the offending character instead.
|
||||
continue
|
||||
}
|
||||
commandLine = append(commandLine, c)
|
||||
}
|
||||
commandLine = append(commandLine, '"')
|
||||
} else {
|
||||
if len(args) == 1 {
|
||||
// args[0] is a valid command line representing itself.
|
||||
// No need to allocate a new slice or string for it.
|
||||
return prog
|
||||
}
|
||||
commandLine = []byte(prog)
|
||||
}
|
||||
|
||||
for _, arg := range args[1:] {
|
||||
commandLine = append(commandLine, ' ')
|
||||
// TODO(bcmills): since we're already appending to a slice, it would be nice
|
||||
// to avoid the intermediate allocations of EscapeArg.
|
||||
// Perhaps we can factor out an appendEscapedArg function.
|
||||
commandLine = append(commandLine, EscapeArg(arg)...)
|
||||
}
|
||||
return string(commandLine)
|
||||
}
|
||||
|
||||
// DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv,
|
||||
// as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that
|
||||
// command lines are passed around.
|
||||
// DecomposeCommandLine returns an error if commandLine contains NUL.
|
||||
func DecomposeCommandLine(commandLine string) ([]string, error) {
|
||||
if len(commandLine) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
utf16CommandLine, err := UTF16FromString(commandLine)
|
||||
if err != nil {
|
||||
return nil, errorspkg.New("string with NUL passed to DecomposeCommandLine")
|
||||
}
|
||||
var argc int32
|
||||
argv, err := commandLineToArgv(&utf16CommandLine[0], &argc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer LocalFree(Handle(unsafe.Pointer(argv)))
|
||||
|
||||
var args []string
|
||||
for _, p := range unsafe.Slice(argv, argc) {
|
||||
args = append(args, UTF16PtrToString(p))
|
||||
}
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// CommandLineToArgv parses a Unicode command line string and sets
|
||||
// argc to the number of parsed arguments.
|
||||
//
|
||||
// The returned memory should be freed using a single call to LocalFree.
|
||||
//
|
||||
// Note that although the return type of CommandLineToArgv indicates 8192
|
||||
// entries of up to 8192 characters each, the actual count of parsed arguments
|
||||
// may exceed 8192, and the documentation for CommandLineToArgvW does not mention
|
||||
// any bound on the lengths of the individual argument strings.
|
||||
// (See https://go.dev/issue/63236.)
|
||||
func CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) {
|
||||
argp, err := commandLineToArgv(cmd, argc)
|
||||
argv = (*[8192]*[8192]uint16)(unsafe.Pointer(argp))
|
||||
return argv, err
|
||||
}
|
||||
|
||||
func CloseOnExec(fd Handle) {
|
||||
SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
|
||||
}
|
||||
|
||||
// FullPath retrieves the full path of the specified file.
|
||||
func FullPath(name string) (path string, err error) {
|
||||
p, err := UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
n := uint32(100)
|
||||
for {
|
||||
buf := make([]uint16, n)
|
||||
n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if n <= uint32(len(buf)) {
|
||||
return UTF16ToString(buf[:n]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes.
|
||||
func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) {
|
||||
var size uintptr
|
||||
err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size)
|
||||
if err != ERROR_INSUFFICIENT_BUFFER {
|
||||
if err == nil {
|
||||
return nil, errorspkg.New("unable to query buffer size from InitializeProcThreadAttributeList")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
alloc, err := LocalAlloc(LMEM_FIXED, uint32(size))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// size is guaranteed to be ≥1 by InitializeProcThreadAttributeList.
|
||||
al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(alloc))}
|
||||
err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return al, err
|
||||
}
|
||||
|
||||
// Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute.
|
||||
func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error {
|
||||
al.pointers = append(al.pointers, value)
|
||||
return updateProcThreadAttribute(al.data, 0, attribute, value, size, nil, nil)
|
||||
}
|
||||
|
||||
// Delete frees ProcThreadAttributeList's resources.
|
||||
func (al *ProcThreadAttributeListContainer) Delete() {
|
||||
deleteProcThreadAttributeList(al.data)
|
||||
LocalFree(Handle(unsafe.Pointer(al.data)))
|
||||
al.data = nil
|
||||
al.pointers = nil
|
||||
}
|
||||
|
||||
// List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx.
|
||||
func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList {
|
||||
return al.data
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
const (
|
||||
MEM_COMMIT = 0x00001000
|
||||
MEM_RESERVE = 0x00002000
|
||||
MEM_DECOMMIT = 0x00004000
|
||||
MEM_RELEASE = 0x00008000
|
||||
MEM_RESET = 0x00080000
|
||||
MEM_TOP_DOWN = 0x00100000
|
||||
MEM_WRITE_WATCH = 0x00200000
|
||||
MEM_PHYSICAL = 0x00400000
|
||||
MEM_RESET_UNDO = 0x01000000
|
||||
MEM_LARGE_PAGES = 0x20000000
|
||||
|
||||
PAGE_NOACCESS = 0x00000001
|
||||
PAGE_READONLY = 0x00000002
|
||||
PAGE_READWRITE = 0x00000004
|
||||
PAGE_WRITECOPY = 0x00000008
|
||||
PAGE_EXECUTE = 0x00000010
|
||||
PAGE_EXECUTE_READ = 0x00000020
|
||||
PAGE_EXECUTE_READWRITE = 0x00000040
|
||||
PAGE_EXECUTE_WRITECOPY = 0x00000080
|
||||
PAGE_GUARD = 0x00000100
|
||||
PAGE_NOCACHE = 0x00000200
|
||||
PAGE_WRITECOMBINE = 0x00000400
|
||||
PAGE_TARGETS_INVALID = 0x40000000
|
||||
PAGE_TARGETS_NO_UPDATE = 0x40000000
|
||||
|
||||
QUOTA_LIMITS_HARDWS_MIN_DISABLE = 0x00000002
|
||||
QUOTA_LIMITS_HARDWS_MIN_ENABLE = 0x00000001
|
||||
QUOTA_LIMITS_HARDWS_MAX_DISABLE = 0x00000008
|
||||
QUOTA_LIMITS_HARDWS_MAX_ENABLE = 0x00000004
|
||||
)
|
||||
|
||||
type MemoryBasicInformation struct {
|
||||
BaseAddress uintptr
|
||||
AllocationBase uintptr
|
||||
AllocationProtect uint32
|
||||
PartitionId uint16
|
||||
RegionSize uintptr
|
||||
State uint32
|
||||
Protect uint32
|
||||
Type uint32
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2019 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
set -e
|
||||
shopt -s nullglob
|
||||
|
||||
winerror="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/shared/winerror.h | sort -Vr | head -n 1)"
|
||||
[[ -n $winerror ]] || { echo "Unable to find winerror.h" >&2; exit 1; }
|
||||
ntstatus="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/shared/ntstatus.h | sort -Vr | head -n 1)"
|
||||
[[ -n $ntstatus ]] || { echo "Unable to find ntstatus.h" >&2; exit 1; }
|
||||
|
||||
declare -A errors
|
||||
|
||||
{
|
||||
echo "// Code generated by 'mkerrors.bash'; DO NOT EDIT."
|
||||
echo
|
||||
echo "package windows"
|
||||
echo "import \"syscall\""
|
||||
echo "const ("
|
||||
|
||||
while read -r line; do
|
||||
unset vtype
|
||||
if [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +([A-Z0-9_]+\()?([A-Z][A-Z0-9_]+k?)\)? ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
value="${BASH_REMATCH[3]}"
|
||||
elif [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +([A-Z0-9_]+\()?((0x)?[0-9A-Fa-f]+)L?\)? ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
value="${BASH_REMATCH[3]}"
|
||||
vtype="${BASH_REMATCH[2]}"
|
||||
elif [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +\(\(([A-Z]+)\)((0x)?[0-9A-Fa-f]+)L?\) ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
value="${BASH_REMATCH[3]}"
|
||||
vtype="${BASH_REMATCH[2]}"
|
||||
else
|
||||
continue
|
||||
fi
|
||||
[[ -n $key && -n $value ]] || continue
|
||||
[[ -z ${errors["$key"]} ]] || continue
|
||||
errors["$key"]="$value"
|
||||
if [[ -v vtype ]]; then
|
||||
if [[ $key == FACILITY_* || $key == NO_ERROR ]]; then
|
||||
vtype=""
|
||||
elif [[ $vtype == *HANDLE* || $vtype == *HRESULT* ]]; then
|
||||
vtype="Handle"
|
||||
else
|
||||
vtype="syscall.Errno"
|
||||
fi
|
||||
last_vtype="$vtype"
|
||||
else
|
||||
vtype=""
|
||||
if [[ $last_vtype == Handle && $value == NO_ERROR ]]; then
|
||||
value="S_OK"
|
||||
elif [[ $last_vtype == syscall.Errno && $value == NO_ERROR ]]; then
|
||||
value="ERROR_SUCCESS"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$key $vtype = $value"
|
||||
done < "$winerror"
|
||||
|
||||
while read -r line; do
|
||||
[[ $line =~ ^#define\ (STATUS_[^\s]+)\ +\(\(NTSTATUS\)((0x)?[0-9a-fA-F]+)L?\) ]] || continue
|
||||
echo "${BASH_REMATCH[1]} NTStatus = ${BASH_REMATCH[2]}"
|
||||
done < "$ntstatus"
|
||||
|
||||
echo ")"
|
||||
} | gofmt > "zerrors_windows.go"
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2019 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
set -e
|
||||
shopt -s nullglob
|
||||
|
||||
knownfolders="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/um/KnownFolders.h | sort -Vr | head -n 1)"
|
||||
[[ -n $knownfolders ]] || { echo "Unable to find KnownFolders.h" >&2; exit 1; }
|
||||
|
||||
{
|
||||
echo "// Code generated by 'mkknownfolderids.bash'; DO NOT EDIT."
|
||||
echo
|
||||
echo "package windows"
|
||||
echo "type KNOWNFOLDERID GUID"
|
||||
echo "var ("
|
||||
while read -r line; do
|
||||
[[ $line =~ DEFINE_KNOWN_FOLDER\((FOLDERID_[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+)\) ]] || continue
|
||||
printf "%s = &KNOWNFOLDERID{0x%08x, 0x%04x, 0x%04x, [8]byte{0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x}}\n" \
|
||||
"${BASH_REMATCH[1]}" $(( "${BASH_REMATCH[2]}" )) $(( "${BASH_REMATCH[3]}" )) $(( "${BASH_REMATCH[4]}" )) \
|
||||
$(( "${BASH_REMATCH[5]}" )) $(( "${BASH_REMATCH[6]}" )) $(( "${BASH_REMATCH[7]}" )) $(( "${BASH_REMATCH[8]}" )) \
|
||||
$(( "${BASH_REMATCH[9]}" )) $(( "${BASH_REMATCH[10]}" )) $(( "${BASH_REMATCH[11]}" )) $(( "${BASH_REMATCH[12]}" ))
|
||||
done < "$knownfolders"
|
||||
echo ")"
|
||||
} | gofmt > "zknownfolderids_windows.go"
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build generate
|
||||
|
||||
package windows
|
||||
|
||||
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go setupapi_windows.go
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && race
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const raceenabled = true
|
||||
|
||||
func raceAcquire(addr unsafe.Pointer) {
|
||||
runtime.RaceAcquire(addr)
|
||||
}
|
||||
|
||||
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||
runtime.RaceReleaseMerge(addr)
|
||||
}
|
||||
|
||||
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||
runtime.RaceReadRange(addr, len)
|
||||
}
|
||||
|
||||
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||
runtime.RaceWriteRange(addr, len)
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && !race
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const raceenabled = false
|
||||
|
||||
func raceAcquire(addr unsafe.Pointer) {
|
||||
}
|
||||
|
||||
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||
}
|
||||
|
||||
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||
}
|
||||
|
||||
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||
}
|
||||
+1435
File diff suppressed because it is too large
Load Diff
+257
@@ -0,0 +1,257 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
const (
|
||||
SC_MANAGER_CONNECT = 1
|
||||
SC_MANAGER_CREATE_SERVICE = 2
|
||||
SC_MANAGER_ENUMERATE_SERVICE = 4
|
||||
SC_MANAGER_LOCK = 8
|
||||
SC_MANAGER_QUERY_LOCK_STATUS = 16
|
||||
SC_MANAGER_MODIFY_BOOT_CONFIG = 32
|
||||
SC_MANAGER_ALL_ACCESS = 0xf003f
|
||||
)
|
||||
|
||||
const (
|
||||
SERVICE_KERNEL_DRIVER = 1
|
||||
SERVICE_FILE_SYSTEM_DRIVER = 2
|
||||
SERVICE_ADAPTER = 4
|
||||
SERVICE_RECOGNIZER_DRIVER = 8
|
||||
SERVICE_WIN32_OWN_PROCESS = 16
|
||||
SERVICE_WIN32_SHARE_PROCESS = 32
|
||||
SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS
|
||||
SERVICE_INTERACTIVE_PROCESS = 256
|
||||
SERVICE_DRIVER = SERVICE_KERNEL_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER
|
||||
SERVICE_TYPE_ALL = SERVICE_WIN32 | SERVICE_ADAPTER | SERVICE_DRIVER | SERVICE_INTERACTIVE_PROCESS
|
||||
|
||||
SERVICE_BOOT_START = 0
|
||||
SERVICE_SYSTEM_START = 1
|
||||
SERVICE_AUTO_START = 2
|
||||
SERVICE_DEMAND_START = 3
|
||||
SERVICE_DISABLED = 4
|
||||
|
||||
SERVICE_ERROR_IGNORE = 0
|
||||
SERVICE_ERROR_NORMAL = 1
|
||||
SERVICE_ERROR_SEVERE = 2
|
||||
SERVICE_ERROR_CRITICAL = 3
|
||||
|
||||
SC_STATUS_PROCESS_INFO = 0
|
||||
|
||||
SC_ACTION_NONE = 0
|
||||
SC_ACTION_RESTART = 1
|
||||
SC_ACTION_REBOOT = 2
|
||||
SC_ACTION_RUN_COMMAND = 3
|
||||
|
||||
SERVICE_STOPPED = 1
|
||||
SERVICE_START_PENDING = 2
|
||||
SERVICE_STOP_PENDING = 3
|
||||
SERVICE_RUNNING = 4
|
||||
SERVICE_CONTINUE_PENDING = 5
|
||||
SERVICE_PAUSE_PENDING = 6
|
||||
SERVICE_PAUSED = 7
|
||||
SERVICE_NO_CHANGE = 0xffffffff
|
||||
|
||||
SERVICE_ACCEPT_STOP = 1
|
||||
SERVICE_ACCEPT_PAUSE_CONTINUE = 2
|
||||
SERVICE_ACCEPT_SHUTDOWN = 4
|
||||
SERVICE_ACCEPT_PARAMCHANGE = 8
|
||||
SERVICE_ACCEPT_NETBINDCHANGE = 16
|
||||
SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 32
|
||||
SERVICE_ACCEPT_POWEREVENT = 64
|
||||
SERVICE_ACCEPT_SESSIONCHANGE = 128
|
||||
SERVICE_ACCEPT_PRESHUTDOWN = 256
|
||||
|
||||
SERVICE_CONTROL_STOP = 1
|
||||
SERVICE_CONTROL_PAUSE = 2
|
||||
SERVICE_CONTROL_CONTINUE = 3
|
||||
SERVICE_CONTROL_INTERROGATE = 4
|
||||
SERVICE_CONTROL_SHUTDOWN = 5
|
||||
SERVICE_CONTROL_PARAMCHANGE = 6
|
||||
SERVICE_CONTROL_NETBINDADD = 7
|
||||
SERVICE_CONTROL_NETBINDREMOVE = 8
|
||||
SERVICE_CONTROL_NETBINDENABLE = 9
|
||||
SERVICE_CONTROL_NETBINDDISABLE = 10
|
||||
SERVICE_CONTROL_DEVICEEVENT = 11
|
||||
SERVICE_CONTROL_HARDWAREPROFILECHANGE = 12
|
||||
SERVICE_CONTROL_POWEREVENT = 13
|
||||
SERVICE_CONTROL_SESSIONCHANGE = 14
|
||||
SERVICE_CONTROL_PRESHUTDOWN = 15
|
||||
|
||||
SERVICE_ACTIVE = 1
|
||||
SERVICE_INACTIVE = 2
|
||||
SERVICE_STATE_ALL = 3
|
||||
|
||||
SERVICE_QUERY_CONFIG = 1
|
||||
SERVICE_CHANGE_CONFIG = 2
|
||||
SERVICE_QUERY_STATUS = 4
|
||||
SERVICE_ENUMERATE_DEPENDENTS = 8
|
||||
SERVICE_START = 16
|
||||
SERVICE_STOP = 32
|
||||
SERVICE_PAUSE_CONTINUE = 64
|
||||
SERVICE_INTERROGATE = 128
|
||||
SERVICE_USER_DEFINED_CONTROL = 256
|
||||
SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_INTERROGATE | SERVICE_USER_DEFINED_CONTROL
|
||||
|
||||
SERVICE_RUNS_IN_SYSTEM_PROCESS = 1
|
||||
|
||||
SERVICE_CONFIG_DESCRIPTION = 1
|
||||
SERVICE_CONFIG_FAILURE_ACTIONS = 2
|
||||
SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3
|
||||
SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 4
|
||||
SERVICE_CONFIG_SERVICE_SID_INFO = 5
|
||||
SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 6
|
||||
SERVICE_CONFIG_PRESHUTDOWN_INFO = 7
|
||||
SERVICE_CONFIG_TRIGGER_INFO = 8
|
||||
SERVICE_CONFIG_PREFERRED_NODE = 9
|
||||
SERVICE_CONFIG_LAUNCH_PROTECTED = 12
|
||||
|
||||
SERVICE_SID_TYPE_NONE = 0
|
||||
SERVICE_SID_TYPE_UNRESTRICTED = 1
|
||||
SERVICE_SID_TYPE_RESTRICTED = 2 | SERVICE_SID_TYPE_UNRESTRICTED
|
||||
|
||||
SC_ENUM_PROCESS_INFO = 0
|
||||
|
||||
SERVICE_NOTIFY_STATUS_CHANGE = 2
|
||||
SERVICE_NOTIFY_STOPPED = 0x00000001
|
||||
SERVICE_NOTIFY_START_PENDING = 0x00000002
|
||||
SERVICE_NOTIFY_STOP_PENDING = 0x00000004
|
||||
SERVICE_NOTIFY_RUNNING = 0x00000008
|
||||
SERVICE_NOTIFY_CONTINUE_PENDING = 0x00000010
|
||||
SERVICE_NOTIFY_PAUSE_PENDING = 0x00000020
|
||||
SERVICE_NOTIFY_PAUSED = 0x00000040
|
||||
SERVICE_NOTIFY_CREATED = 0x00000080
|
||||
SERVICE_NOTIFY_DELETED = 0x00000100
|
||||
SERVICE_NOTIFY_DELETE_PENDING = 0x00000200
|
||||
|
||||
SC_EVENT_DATABASE_CHANGE = 0
|
||||
SC_EVENT_PROPERTY_CHANGE = 1
|
||||
SC_EVENT_STATUS_CHANGE = 2
|
||||
|
||||
SERVICE_START_REASON_DEMAND = 0x00000001
|
||||
SERVICE_START_REASON_AUTO = 0x00000002
|
||||
SERVICE_START_REASON_TRIGGER = 0x00000004
|
||||
SERVICE_START_REASON_RESTART_ON_FAILURE = 0x00000008
|
||||
SERVICE_START_REASON_DELAYEDAUTO = 0x00000010
|
||||
|
||||
SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON = 1
|
||||
)
|
||||
|
||||
type ENUM_SERVICE_STATUS struct {
|
||||
ServiceName *uint16
|
||||
DisplayName *uint16
|
||||
ServiceStatus SERVICE_STATUS
|
||||
}
|
||||
|
||||
type SERVICE_STATUS struct {
|
||||
ServiceType uint32
|
||||
CurrentState uint32
|
||||
ControlsAccepted uint32
|
||||
Win32ExitCode uint32
|
||||
ServiceSpecificExitCode uint32
|
||||
CheckPoint uint32
|
||||
WaitHint uint32
|
||||
}
|
||||
|
||||
type SERVICE_TABLE_ENTRY struct {
|
||||
ServiceName *uint16
|
||||
ServiceProc uintptr
|
||||
}
|
||||
|
||||
type QUERY_SERVICE_CONFIG struct {
|
||||
ServiceType uint32
|
||||
StartType uint32
|
||||
ErrorControl uint32
|
||||
BinaryPathName *uint16
|
||||
LoadOrderGroup *uint16
|
||||
TagId uint32
|
||||
Dependencies *uint16
|
||||
ServiceStartName *uint16
|
||||
DisplayName *uint16
|
||||
}
|
||||
|
||||
type SERVICE_DESCRIPTION struct {
|
||||
Description *uint16
|
||||
}
|
||||
|
||||
type SERVICE_DELAYED_AUTO_START_INFO struct {
|
||||
IsDelayedAutoStartUp uint32
|
||||
}
|
||||
|
||||
type SERVICE_STATUS_PROCESS struct {
|
||||
ServiceType uint32
|
||||
CurrentState uint32
|
||||
ControlsAccepted uint32
|
||||
Win32ExitCode uint32
|
||||
ServiceSpecificExitCode uint32
|
||||
CheckPoint uint32
|
||||
WaitHint uint32
|
||||
ProcessId uint32
|
||||
ServiceFlags uint32
|
||||
}
|
||||
|
||||
type ENUM_SERVICE_STATUS_PROCESS struct {
|
||||
ServiceName *uint16
|
||||
DisplayName *uint16
|
||||
ServiceStatusProcess SERVICE_STATUS_PROCESS
|
||||
}
|
||||
|
||||
type SERVICE_NOTIFY struct {
|
||||
Version uint32
|
||||
NotifyCallback uintptr
|
||||
Context uintptr
|
||||
NotificationStatus uint32
|
||||
ServiceStatus SERVICE_STATUS_PROCESS
|
||||
NotificationTriggered uint32
|
||||
ServiceNames *uint16
|
||||
}
|
||||
|
||||
type SERVICE_FAILURE_ACTIONS struct {
|
||||
ResetPeriod uint32
|
||||
RebootMsg *uint16
|
||||
Command *uint16
|
||||
ActionsCount uint32
|
||||
Actions *SC_ACTION
|
||||
}
|
||||
|
||||
type SERVICE_FAILURE_ACTIONS_FLAG struct {
|
||||
FailureActionsOnNonCrashFailures int32
|
||||
}
|
||||
|
||||
type SC_ACTION struct {
|
||||
Type uint32
|
||||
Delay uint32
|
||||
}
|
||||
|
||||
type QUERY_SERVICE_LOCK_STATUS struct {
|
||||
IsLocked uint32
|
||||
LockOwner *uint16
|
||||
LockDuration uint32
|
||||
}
|
||||
|
||||
//sys OpenSCManager(machineName *uint16, databaseName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenSCManagerW
|
||||
//sys CloseServiceHandle(handle Handle) (err error) = advapi32.CloseServiceHandle
|
||||
//sys CreateService(mgr Handle, serviceName *uint16, displayName *uint16, access uint32, srvType uint32, startType uint32, errCtl uint32, pathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16) (handle Handle, err error) [failretval==0] = advapi32.CreateServiceW
|
||||
//sys OpenService(mgr Handle, serviceName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenServiceW
|
||||
//sys DeleteService(service Handle) (err error) = advapi32.DeleteService
|
||||
//sys StartService(service Handle, numArgs uint32, argVectors **uint16) (err error) = advapi32.StartServiceW
|
||||
//sys QueryServiceStatus(service Handle, status *SERVICE_STATUS) (err error) = advapi32.QueryServiceStatus
|
||||
//sys QueryServiceLockStatus(mgr Handle, lockStatus *QUERY_SERVICE_LOCK_STATUS, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceLockStatusW
|
||||
//sys ControlService(service Handle, control uint32, status *SERVICE_STATUS) (err error) = advapi32.ControlService
|
||||
//sys StartServiceCtrlDispatcher(serviceTable *SERVICE_TABLE_ENTRY) (err error) = advapi32.StartServiceCtrlDispatcherW
|
||||
//sys SetServiceStatus(service Handle, serviceStatus *SERVICE_STATUS) (err error) = advapi32.SetServiceStatus
|
||||
//sys ChangeServiceConfig(service Handle, serviceType uint32, startType uint32, errorControl uint32, binaryPathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16, displayName *uint16) (err error) = advapi32.ChangeServiceConfigW
|
||||
//sys QueryServiceConfig(service Handle, serviceConfig *QUERY_SERVICE_CONFIG, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfigW
|
||||
//sys ChangeServiceConfig2(service Handle, infoLevel uint32, info *byte) (err error) = advapi32.ChangeServiceConfig2W
|
||||
//sys QueryServiceConfig2(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfig2W
|
||||
//sys EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) = advapi32.EnumServicesStatusExW
|
||||
//sys QueryServiceStatusEx(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceStatusEx
|
||||
//sys NotifyServiceStatusChange(service Handle, notifyMask uint32, notifier *SERVICE_NOTIFY) (ret error) = advapi32.NotifyServiceStatusChangeW
|
||||
//sys SubscribeServiceChangeNotifications(service Handle, eventType uint32, callback uintptr, callbackCtx uintptr, subscription *uintptr) (ret error) = sechost.SubscribeServiceChangeNotifications?
|
||||
//sys UnsubscribeServiceChangeNotifications(subscription uintptr) = sechost.UnsubscribeServiceChangeNotifications?
|
||||
//sys RegisterServiceCtrlHandlerEx(serviceName *uint16, handlerProc uintptr, context uintptr) (handle Handle, err error) = advapi32.RegisterServiceCtrlHandlerExW
|
||||
//sys QueryServiceDynamicInformation(service Handle, infoLevel uint32, dynamicInfo unsafe.Pointer) (err error) = advapi32.QueryServiceDynamicInformation?
|
||||
//sys EnumDependentServices(service Handle, activityState uint32, services *ENUM_SERVICE_STATUS, buffSize uint32, bytesNeeded *uint32, servicesReturned *uint32) (err error) = advapi32.EnumDependentServicesW
|
||||
+1425
File diff suppressed because it is too large
Load Diff
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
func itoa(val int) string { // do it here rather than with fmt to avoid dependency
|
||||
if val < 0 {
|
||||
return "-" + itoa(-val)
|
||||
}
|
||||
var buf [32]byte // big enough for int64
|
||||
i := len(buf) - 1
|
||||
for val >= 10 {
|
||||
buf[i] = byte(val%10 + '0')
|
||||
i--
|
||||
val /= 10
|
||||
}
|
||||
buf[i] = byte(val + '0')
|
||||
return string(buf[i:])
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
// Package windows contains an interface to the low-level operating system
|
||||
// primitives. OS details vary depending on the underlying system, and
|
||||
// by default, godoc will display the OS-specific documentation for the current
|
||||
// system. If you want godoc to display syscall documentation for another
|
||||
// system, set $GOOS and $GOARCH to the desired system. For example, if
|
||||
// you want to view documentation for freebsd/arm on linux/amd64, set $GOOS
|
||||
// to freebsd and $GOARCH to arm.
|
||||
//
|
||||
// The primary use of this package is inside other packages that provide a more
|
||||
// portable interface to the system, such as "os", "time" and "net". Use
|
||||
// those packages rather than this one if you can.
|
||||
//
|
||||
// For details of the functions and data types in this package consult
|
||||
// the manuals for the appropriate operating system.
|
||||
//
|
||||
// These calls return err == nil to indicate success; otherwise
|
||||
// err represents an operating system error describing the failure and
|
||||
// holds a value of type syscall.Errno.
|
||||
package windows // import "golang.org/x/sys/windows"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ByteSliceFromString returns a NUL-terminated slice of bytes
|
||||
// containing the text of s. If s contains a NUL byte at any
|
||||
// location, it returns (nil, syscall.EINVAL).
|
||||
func ByteSliceFromString(s string) ([]byte, error) {
|
||||
if strings.IndexByte(s, 0) != -1 {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
a := make([]byte, len(s)+1)
|
||||
copy(a, s)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// BytePtrFromString returns a pointer to a NUL-terminated array of
|
||||
// bytes containing the text of s. If s contains a NUL byte at any
|
||||
// location, it returns (nil, syscall.EINVAL).
|
||||
func BytePtrFromString(s string) (*byte, error) {
|
||||
a, err := ByteSliceFromString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &a[0], nil
|
||||
}
|
||||
|
||||
// ByteSliceToString returns a string form of the text represented by the slice s, with a terminating NUL and any
|
||||
// bytes after the NUL removed.
|
||||
func ByteSliceToString(s []byte) string {
|
||||
if i := bytes.IndexByte(s, 0); i != -1 {
|
||||
s = s[:i]
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// BytePtrToString takes a pointer to a sequence of text and returns the corresponding string.
|
||||
// If the pointer is nil, it returns the empty string. It assumes that the text sequence is terminated
|
||||
// at a zero byte; if the zero byte is not present, the program may crash.
|
||||
func BytePtrToString(p *byte) string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
if *p == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Find NUL terminator.
|
||||
n := 0
|
||||
for ptr := unsafe.Pointer(p); *(*byte)(ptr) != 0; n++ {
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + 1)
|
||||
}
|
||||
|
||||
return string(unsafe.Slice(p, n))
|
||||
}
|
||||
|
||||
// Single-word zero for use when we need a valid pointer to 0 bytes.
|
||||
// See mksyscall.pl.
|
||||
var _zero uintptr
|
||||
|
||||
func (ts *Timespec) Unix() (sec int64, nsec int64) {
|
||||
return int64(ts.Sec), int64(ts.Nsec)
|
||||
}
|
||||
|
||||
func (tv *Timeval) Unix() (sec int64, nsec int64) {
|
||||
return int64(tv.Sec), int64(tv.Usec) * 1000
|
||||
}
|
||||
|
||||
func (ts *Timespec) Nano() int64 {
|
||||
return int64(ts.Sec)*1e9 + int64(ts.Nsec)
|
||||
}
|
||||
|
||||
func (tv *Timeval) Nano() int64 {
|
||||
return int64(tv.Sec)*1e9 + int64(tv.Usec)*1000
|
||||
}
|
||||
+1836
File diff suppressed because it is too large
Load Diff
+3382
File diff suppressed because it is too large
Load Diff
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Port uint16
|
||||
Proto *byte
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
_ uint32 // pad to 8 byte boundary
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Proto *byte
|
||||
Port uint16
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Port uint16
|
||||
Proto *byte
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
_ uint32 // pad to 8 byte boundary
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Proto *byte
|
||||
Port uint16
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
}
|
||||
+9468
File diff suppressed because it is too large
Load Diff
+149
@@ -0,0 +1,149 @@
|
||||
// Code generated by 'mkknownfolderids.bash'; DO NOT EDIT.
|
||||
|
||||
package windows
|
||||
|
||||
type KNOWNFOLDERID GUID
|
||||
|
||||
var (
|
||||
FOLDERID_NetworkFolder = &KNOWNFOLDERID{0xd20beec4, 0x5ca8, 0x4905, [8]byte{0xae, 0x3b, 0xbf, 0x25, 0x1e, 0xa0, 0x9b, 0x53}}
|
||||
FOLDERID_ComputerFolder = &KNOWNFOLDERID{0x0ac0837c, 0xbbf8, 0x452a, [8]byte{0x85, 0x0d, 0x79, 0xd0, 0x8e, 0x66, 0x7c, 0xa7}}
|
||||
FOLDERID_InternetFolder = &KNOWNFOLDERID{0x4d9f7874, 0x4e0c, 0x4904, [8]byte{0x96, 0x7b, 0x40, 0xb0, 0xd2, 0x0c, 0x3e, 0x4b}}
|
||||
FOLDERID_ControlPanelFolder = &KNOWNFOLDERID{0x82a74aeb, 0xaeb4, 0x465c, [8]byte{0xa0, 0x14, 0xd0, 0x97, 0xee, 0x34, 0x6d, 0x63}}
|
||||
FOLDERID_PrintersFolder = &KNOWNFOLDERID{0x76fc4e2d, 0xd6ad, 0x4519, [8]byte{0xa6, 0x63, 0x37, 0xbd, 0x56, 0x06, 0x81, 0x85}}
|
||||
FOLDERID_SyncManagerFolder = &KNOWNFOLDERID{0x43668bf8, 0xc14e, 0x49b2, [8]byte{0x97, 0xc9, 0x74, 0x77, 0x84, 0xd7, 0x84, 0xb7}}
|
||||
FOLDERID_SyncSetupFolder = &KNOWNFOLDERID{0x0f214138, 0xb1d3, 0x4a90, [8]byte{0xbb, 0xa9, 0x27, 0xcb, 0xc0, 0xc5, 0x38, 0x9a}}
|
||||
FOLDERID_ConflictFolder = &KNOWNFOLDERID{0x4bfefb45, 0x347d, 0x4006, [8]byte{0xa5, 0xbe, 0xac, 0x0c, 0xb0, 0x56, 0x71, 0x92}}
|
||||
FOLDERID_SyncResultsFolder = &KNOWNFOLDERID{0x289a9a43, 0xbe44, 0x4057, [8]byte{0xa4, 0x1b, 0x58, 0x7a, 0x76, 0xd7, 0xe7, 0xf9}}
|
||||
FOLDERID_RecycleBinFolder = &KNOWNFOLDERID{0xb7534046, 0x3ecb, 0x4c18, [8]byte{0xbe, 0x4e, 0x64, 0xcd, 0x4c, 0xb7, 0xd6, 0xac}}
|
||||
FOLDERID_ConnectionsFolder = &KNOWNFOLDERID{0x6f0cd92b, 0x2e97, 0x45d1, [8]byte{0x88, 0xff, 0xb0, 0xd1, 0x86, 0xb8, 0xde, 0xdd}}
|
||||
FOLDERID_Fonts = &KNOWNFOLDERID{0xfd228cb7, 0xae11, 0x4ae3, [8]byte{0x86, 0x4c, 0x16, 0xf3, 0x91, 0x0a, 0xb8, 0xfe}}
|
||||
FOLDERID_Desktop = &KNOWNFOLDERID{0xb4bfcc3a, 0xdb2c, 0x424c, [8]byte{0xb0, 0x29, 0x7f, 0xe9, 0x9a, 0x87, 0xc6, 0x41}}
|
||||
FOLDERID_Startup = &KNOWNFOLDERID{0xb97d20bb, 0xf46a, 0x4c97, [8]byte{0xba, 0x10, 0x5e, 0x36, 0x08, 0x43, 0x08, 0x54}}
|
||||
FOLDERID_Programs = &KNOWNFOLDERID{0xa77f5d77, 0x2e2b, 0x44c3, [8]byte{0xa6, 0xa2, 0xab, 0xa6, 0x01, 0x05, 0x4a, 0x51}}
|
||||
FOLDERID_StartMenu = &KNOWNFOLDERID{0x625b53c3, 0xab48, 0x4ec1, [8]byte{0xba, 0x1f, 0xa1, 0xef, 0x41, 0x46, 0xfc, 0x19}}
|
||||
FOLDERID_Recent = &KNOWNFOLDERID{0xae50c081, 0xebd2, 0x438a, [8]byte{0x86, 0x55, 0x8a, 0x09, 0x2e, 0x34, 0x98, 0x7a}}
|
||||
FOLDERID_SendTo = &KNOWNFOLDERID{0x8983036c, 0x27c0, 0x404b, [8]byte{0x8f, 0x08, 0x10, 0x2d, 0x10, 0xdc, 0xfd, 0x74}}
|
||||
FOLDERID_Documents = &KNOWNFOLDERID{0xfdd39ad0, 0x238f, 0x46af, [8]byte{0xad, 0xb4, 0x6c, 0x85, 0x48, 0x03, 0x69, 0xc7}}
|
||||
FOLDERID_Favorites = &KNOWNFOLDERID{0x1777f761, 0x68ad, 0x4d8a, [8]byte{0x87, 0xbd, 0x30, 0xb7, 0x59, 0xfa, 0x33, 0xdd}}
|
||||
FOLDERID_NetHood = &KNOWNFOLDERID{0xc5abbf53, 0xe17f, 0x4121, [8]byte{0x89, 0x00, 0x86, 0x62, 0x6f, 0xc2, 0xc9, 0x73}}
|
||||
FOLDERID_PrintHood = &KNOWNFOLDERID{0x9274bd8d, 0xcfd1, 0x41c3, [8]byte{0xb3, 0x5e, 0xb1, 0x3f, 0x55, 0xa7, 0x58, 0xf4}}
|
||||
FOLDERID_Templates = &KNOWNFOLDERID{0xa63293e8, 0x664e, 0x48db, [8]byte{0xa0, 0x79, 0xdf, 0x75, 0x9e, 0x05, 0x09, 0xf7}}
|
||||
FOLDERID_CommonStartup = &KNOWNFOLDERID{0x82a5ea35, 0xd9cd, 0x47c5, [8]byte{0x96, 0x29, 0xe1, 0x5d, 0x2f, 0x71, 0x4e, 0x6e}}
|
||||
FOLDERID_CommonPrograms = &KNOWNFOLDERID{0x0139d44e, 0x6afe, 0x49f2, [8]byte{0x86, 0x90, 0x3d, 0xaf, 0xca, 0xe6, 0xff, 0xb8}}
|
||||
FOLDERID_CommonStartMenu = &KNOWNFOLDERID{0xa4115719, 0xd62e, 0x491d, [8]byte{0xaa, 0x7c, 0xe7, 0x4b, 0x8b, 0xe3, 0xb0, 0x67}}
|
||||
FOLDERID_PublicDesktop = &KNOWNFOLDERID{0xc4aa340d, 0xf20f, 0x4863, [8]byte{0xaf, 0xef, 0xf8, 0x7e, 0xf2, 0xe6, 0xba, 0x25}}
|
||||
FOLDERID_ProgramData = &KNOWNFOLDERID{0x62ab5d82, 0xfdc1, 0x4dc3, [8]byte{0xa9, 0xdd, 0x07, 0x0d, 0x1d, 0x49, 0x5d, 0x97}}
|
||||
FOLDERID_CommonTemplates = &KNOWNFOLDERID{0xb94237e7, 0x57ac, 0x4347, [8]byte{0x91, 0x51, 0xb0, 0x8c, 0x6c, 0x32, 0xd1, 0xf7}}
|
||||
FOLDERID_PublicDocuments = &KNOWNFOLDERID{0xed4824af, 0xdce4, 0x45a8, [8]byte{0x81, 0xe2, 0xfc, 0x79, 0x65, 0x08, 0x36, 0x34}}
|
||||
FOLDERID_RoamingAppData = &KNOWNFOLDERID{0x3eb685db, 0x65f9, 0x4cf6, [8]byte{0xa0, 0x3a, 0xe3, 0xef, 0x65, 0x72, 0x9f, 0x3d}}
|
||||
FOLDERID_LocalAppData = &KNOWNFOLDERID{0xf1b32785, 0x6fba, 0x4fcf, [8]byte{0x9d, 0x55, 0x7b, 0x8e, 0x7f, 0x15, 0x70, 0x91}}
|
||||
FOLDERID_LocalAppDataLow = &KNOWNFOLDERID{0xa520a1a4, 0x1780, 0x4ff6, [8]byte{0xbd, 0x18, 0x16, 0x73, 0x43, 0xc5, 0xaf, 0x16}}
|
||||
FOLDERID_InternetCache = &KNOWNFOLDERID{0x352481e8, 0x33be, 0x4251, [8]byte{0xba, 0x85, 0x60, 0x07, 0xca, 0xed, 0xcf, 0x9d}}
|
||||
FOLDERID_Cookies = &KNOWNFOLDERID{0x2b0f765d, 0xc0e9, 0x4171, [8]byte{0x90, 0x8e, 0x08, 0xa6, 0x11, 0xb8, 0x4f, 0xf6}}
|
||||
FOLDERID_History = &KNOWNFOLDERID{0xd9dc8a3b, 0xb784, 0x432e, [8]byte{0xa7, 0x81, 0x5a, 0x11, 0x30, 0xa7, 0x59, 0x63}}
|
||||
FOLDERID_System = &KNOWNFOLDERID{0x1ac14e77, 0x02e7, 0x4e5d, [8]byte{0xb7, 0x44, 0x2e, 0xb1, 0xae, 0x51, 0x98, 0xb7}}
|
||||
FOLDERID_SystemX86 = &KNOWNFOLDERID{0xd65231b0, 0xb2f1, 0x4857, [8]byte{0xa4, 0xce, 0xa8, 0xe7, 0xc6, 0xea, 0x7d, 0x27}}
|
||||
FOLDERID_Windows = &KNOWNFOLDERID{0xf38bf404, 0x1d43, 0x42f2, [8]byte{0x93, 0x05, 0x67, 0xde, 0x0b, 0x28, 0xfc, 0x23}}
|
||||
FOLDERID_Profile = &KNOWNFOLDERID{0x5e6c858f, 0x0e22, 0x4760, [8]byte{0x9a, 0xfe, 0xea, 0x33, 0x17, 0xb6, 0x71, 0x73}}
|
||||
FOLDERID_Pictures = &KNOWNFOLDERID{0x33e28130, 0x4e1e, 0x4676, [8]byte{0x83, 0x5a, 0x98, 0x39, 0x5c, 0x3b, 0xc3, 0xbb}}
|
||||
FOLDERID_ProgramFilesX86 = &KNOWNFOLDERID{0x7c5a40ef, 0xa0fb, 0x4bfc, [8]byte{0x87, 0x4a, 0xc0, 0xf2, 0xe0, 0xb9, 0xfa, 0x8e}}
|
||||
FOLDERID_ProgramFilesCommonX86 = &KNOWNFOLDERID{0xde974d24, 0xd9c6, 0x4d3e, [8]byte{0xbf, 0x91, 0xf4, 0x45, 0x51, 0x20, 0xb9, 0x17}}
|
||||
FOLDERID_ProgramFilesX64 = &KNOWNFOLDERID{0x6d809377, 0x6af0, 0x444b, [8]byte{0x89, 0x57, 0xa3, 0x77, 0x3f, 0x02, 0x20, 0x0e}}
|
||||
FOLDERID_ProgramFilesCommonX64 = &KNOWNFOLDERID{0x6365d5a7, 0x0f0d, 0x45e5, [8]byte{0x87, 0xf6, 0x0d, 0xa5, 0x6b, 0x6a, 0x4f, 0x7d}}
|
||||
FOLDERID_ProgramFiles = &KNOWNFOLDERID{0x905e63b6, 0xc1bf, 0x494e, [8]byte{0xb2, 0x9c, 0x65, 0xb7, 0x32, 0xd3, 0xd2, 0x1a}}
|
||||
FOLDERID_ProgramFilesCommon = &KNOWNFOLDERID{0xf7f1ed05, 0x9f6d, 0x47a2, [8]byte{0xaa, 0xae, 0x29, 0xd3, 0x17, 0xc6, 0xf0, 0x66}}
|
||||
FOLDERID_UserProgramFiles = &KNOWNFOLDERID{0x5cd7aee2, 0x2219, 0x4a67, [8]byte{0xb8, 0x5d, 0x6c, 0x9c, 0xe1, 0x56, 0x60, 0xcb}}
|
||||
FOLDERID_UserProgramFilesCommon = &KNOWNFOLDERID{0xbcbd3057, 0xca5c, 0x4622, [8]byte{0xb4, 0x2d, 0xbc, 0x56, 0xdb, 0x0a, 0xe5, 0x16}}
|
||||
FOLDERID_AdminTools = &KNOWNFOLDERID{0x724ef170, 0xa42d, 0x4fef, [8]byte{0x9f, 0x26, 0xb6, 0x0e, 0x84, 0x6f, 0xba, 0x4f}}
|
||||
FOLDERID_CommonAdminTools = &KNOWNFOLDERID{0xd0384e7d, 0xbac3, 0x4797, [8]byte{0x8f, 0x14, 0xcb, 0xa2, 0x29, 0xb3, 0x92, 0xb5}}
|
||||
FOLDERID_Music = &KNOWNFOLDERID{0x4bd8d571, 0x6d19, 0x48d3, [8]byte{0xbe, 0x97, 0x42, 0x22, 0x20, 0x08, 0x0e, 0x43}}
|
||||
FOLDERID_Videos = &KNOWNFOLDERID{0x18989b1d, 0x99b5, 0x455b, [8]byte{0x84, 0x1c, 0xab, 0x7c, 0x74, 0xe4, 0xdd, 0xfc}}
|
||||
FOLDERID_Ringtones = &KNOWNFOLDERID{0xc870044b, 0xf49e, 0x4126, [8]byte{0xa9, 0xc3, 0xb5, 0x2a, 0x1f, 0xf4, 0x11, 0xe8}}
|
||||
FOLDERID_PublicPictures = &KNOWNFOLDERID{0xb6ebfb86, 0x6907, 0x413c, [8]byte{0x9a, 0xf7, 0x4f, 0xc2, 0xab, 0xf0, 0x7c, 0xc5}}
|
||||
FOLDERID_PublicMusic = &KNOWNFOLDERID{0x3214fab5, 0x9757, 0x4298, [8]byte{0xbb, 0x61, 0x92, 0xa9, 0xde, 0xaa, 0x44, 0xff}}
|
||||
FOLDERID_PublicVideos = &KNOWNFOLDERID{0x2400183a, 0x6185, 0x49fb, [8]byte{0xa2, 0xd8, 0x4a, 0x39, 0x2a, 0x60, 0x2b, 0xa3}}
|
||||
FOLDERID_PublicRingtones = &KNOWNFOLDERID{0xe555ab60, 0x153b, 0x4d17, [8]byte{0x9f, 0x04, 0xa5, 0xfe, 0x99, 0xfc, 0x15, 0xec}}
|
||||
FOLDERID_ResourceDir = &KNOWNFOLDERID{0x8ad10c31, 0x2adb, 0x4296, [8]byte{0xa8, 0xf7, 0xe4, 0x70, 0x12, 0x32, 0xc9, 0x72}}
|
||||
FOLDERID_LocalizedResourcesDir = &KNOWNFOLDERID{0x2a00375e, 0x224c, 0x49de, [8]byte{0xb8, 0xd1, 0x44, 0x0d, 0xf7, 0xef, 0x3d, 0xdc}}
|
||||
FOLDERID_CommonOEMLinks = &KNOWNFOLDERID{0xc1bae2d0, 0x10df, 0x4334, [8]byte{0xbe, 0xdd, 0x7a, 0xa2, 0x0b, 0x22, 0x7a, 0x9d}}
|
||||
FOLDERID_CDBurning = &KNOWNFOLDERID{0x9e52ab10, 0xf80d, 0x49df, [8]byte{0xac, 0xb8, 0x43, 0x30, 0xf5, 0x68, 0x78, 0x55}}
|
||||
FOLDERID_UserProfiles = &KNOWNFOLDERID{0x0762d272, 0xc50a, 0x4bb0, [8]byte{0xa3, 0x82, 0x69, 0x7d, 0xcd, 0x72, 0x9b, 0x80}}
|
||||
FOLDERID_Playlists = &KNOWNFOLDERID{0xde92c1c7, 0x837f, 0x4f69, [8]byte{0xa3, 0xbb, 0x86, 0xe6, 0x31, 0x20, 0x4a, 0x23}}
|
||||
FOLDERID_SamplePlaylists = &KNOWNFOLDERID{0x15ca69b3, 0x30ee, 0x49c1, [8]byte{0xac, 0xe1, 0x6b, 0x5e, 0xc3, 0x72, 0xaf, 0xb5}}
|
||||
FOLDERID_SampleMusic = &KNOWNFOLDERID{0xb250c668, 0xf57d, 0x4ee1, [8]byte{0xa6, 0x3c, 0x29, 0x0e, 0xe7, 0xd1, 0xaa, 0x1f}}
|
||||
FOLDERID_SamplePictures = &KNOWNFOLDERID{0xc4900540, 0x2379, 0x4c75, [8]byte{0x84, 0x4b, 0x64, 0xe6, 0xfa, 0xf8, 0x71, 0x6b}}
|
||||
FOLDERID_SampleVideos = &KNOWNFOLDERID{0x859ead94, 0x2e85, 0x48ad, [8]byte{0xa7, 0x1a, 0x09, 0x69, 0xcb, 0x56, 0xa6, 0xcd}}
|
||||
FOLDERID_PhotoAlbums = &KNOWNFOLDERID{0x69d2cf90, 0xfc33, 0x4fb7, [8]byte{0x9a, 0x0c, 0xeb, 0xb0, 0xf0, 0xfc, 0xb4, 0x3c}}
|
||||
FOLDERID_Public = &KNOWNFOLDERID{0xdfdf76a2, 0xc82a, 0x4d63, [8]byte{0x90, 0x6a, 0x56, 0x44, 0xac, 0x45, 0x73, 0x85}}
|
||||
FOLDERID_ChangeRemovePrograms = &KNOWNFOLDERID{0xdf7266ac, 0x9274, 0x4867, [8]byte{0x8d, 0x55, 0x3b, 0xd6, 0x61, 0xde, 0x87, 0x2d}}
|
||||
FOLDERID_AppUpdates = &KNOWNFOLDERID{0xa305ce99, 0xf527, 0x492b, [8]byte{0x8b, 0x1a, 0x7e, 0x76, 0xfa, 0x98, 0xd6, 0xe4}}
|
||||
FOLDERID_AddNewPrograms = &KNOWNFOLDERID{0xde61d971, 0x5ebc, 0x4f02, [8]byte{0xa3, 0xa9, 0x6c, 0x82, 0x89, 0x5e, 0x5c, 0x04}}
|
||||
FOLDERID_Downloads = &KNOWNFOLDERID{0x374de290, 0x123f, 0x4565, [8]byte{0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b}}
|
||||
FOLDERID_PublicDownloads = &KNOWNFOLDERID{0x3d644c9b, 0x1fb8, 0x4f30, [8]byte{0x9b, 0x45, 0xf6, 0x70, 0x23, 0x5f, 0x79, 0xc0}}
|
||||
FOLDERID_SavedSearches = &KNOWNFOLDERID{0x7d1d3a04, 0xdebb, 0x4115, [8]byte{0x95, 0xcf, 0x2f, 0x29, 0xda, 0x29, 0x20, 0xda}}
|
||||
FOLDERID_QuickLaunch = &KNOWNFOLDERID{0x52a4f021, 0x7b75, 0x48a9, [8]byte{0x9f, 0x6b, 0x4b, 0x87, 0xa2, 0x10, 0xbc, 0x8f}}
|
||||
FOLDERID_Contacts = &KNOWNFOLDERID{0x56784854, 0xc6cb, 0x462b, [8]byte{0x81, 0x69, 0x88, 0xe3, 0x50, 0xac, 0xb8, 0x82}}
|
||||
FOLDERID_SidebarParts = &KNOWNFOLDERID{0xa75d362e, 0x50fc, 0x4fb7, [8]byte{0xac, 0x2c, 0xa8, 0xbe, 0xaa, 0x31, 0x44, 0x93}}
|
||||
FOLDERID_SidebarDefaultParts = &KNOWNFOLDERID{0x7b396e54, 0x9ec5, 0x4300, [8]byte{0xbe, 0x0a, 0x24, 0x82, 0xeb, 0xae, 0x1a, 0x26}}
|
||||
FOLDERID_PublicGameTasks = &KNOWNFOLDERID{0xdebf2536, 0xe1a8, 0x4c59, [8]byte{0xb6, 0xa2, 0x41, 0x45, 0x86, 0x47, 0x6a, 0xea}}
|
||||
FOLDERID_GameTasks = &KNOWNFOLDERID{0x054fae61, 0x4dd8, 0x4787, [8]byte{0x80, 0xb6, 0x09, 0x02, 0x20, 0xc4, 0xb7, 0x00}}
|
||||
FOLDERID_SavedGames = &KNOWNFOLDERID{0x4c5c32ff, 0xbb9d, 0x43b0, [8]byte{0xb5, 0xb4, 0x2d, 0x72, 0xe5, 0x4e, 0xaa, 0xa4}}
|
||||
FOLDERID_Games = &KNOWNFOLDERID{0xcac52c1a, 0xb53d, 0x4edc, [8]byte{0x92, 0xd7, 0x6b, 0x2e, 0x8a, 0xc1, 0x94, 0x34}}
|
||||
FOLDERID_SEARCH_MAPI = &KNOWNFOLDERID{0x98ec0e18, 0x2098, 0x4d44, [8]byte{0x86, 0x44, 0x66, 0x97, 0x93, 0x15, 0xa2, 0x81}}
|
||||
FOLDERID_SEARCH_CSC = &KNOWNFOLDERID{0xee32e446, 0x31ca, 0x4aba, [8]byte{0x81, 0x4f, 0xa5, 0xeb, 0xd2, 0xfd, 0x6d, 0x5e}}
|
||||
FOLDERID_Links = &KNOWNFOLDERID{0xbfb9d5e0, 0xc6a9, 0x404c, [8]byte{0xb2, 0xb2, 0xae, 0x6d, 0xb6, 0xaf, 0x49, 0x68}}
|
||||
FOLDERID_UsersFiles = &KNOWNFOLDERID{0xf3ce0f7c, 0x4901, 0x4acc, [8]byte{0x86, 0x48, 0xd5, 0xd4, 0x4b, 0x04, 0xef, 0x8f}}
|
||||
FOLDERID_UsersLibraries = &KNOWNFOLDERID{0xa302545d, 0xdeff, 0x464b, [8]byte{0xab, 0xe8, 0x61, 0xc8, 0x64, 0x8d, 0x93, 0x9b}}
|
||||
FOLDERID_SearchHome = &KNOWNFOLDERID{0x190337d1, 0xb8ca, 0x4121, [8]byte{0xa6, 0x39, 0x6d, 0x47, 0x2d, 0x16, 0x97, 0x2a}}
|
||||
FOLDERID_OriginalImages = &KNOWNFOLDERID{0x2c36c0aa, 0x5812, 0x4b87, [8]byte{0xbf, 0xd0, 0x4c, 0xd0, 0xdf, 0xb1, 0x9b, 0x39}}
|
||||
FOLDERID_DocumentsLibrary = &KNOWNFOLDERID{0x7b0db17d, 0x9cd2, 0x4a93, [8]byte{0x97, 0x33, 0x46, 0xcc, 0x89, 0x02, 0x2e, 0x7c}}
|
||||
FOLDERID_MusicLibrary = &KNOWNFOLDERID{0x2112ab0a, 0xc86a, 0x4ffe, [8]byte{0xa3, 0x68, 0x0d, 0xe9, 0x6e, 0x47, 0x01, 0x2e}}
|
||||
FOLDERID_PicturesLibrary = &KNOWNFOLDERID{0xa990ae9f, 0xa03b, 0x4e80, [8]byte{0x94, 0xbc, 0x99, 0x12, 0xd7, 0x50, 0x41, 0x04}}
|
||||
FOLDERID_VideosLibrary = &KNOWNFOLDERID{0x491e922f, 0x5643, 0x4af4, [8]byte{0xa7, 0xeb, 0x4e, 0x7a, 0x13, 0x8d, 0x81, 0x74}}
|
||||
FOLDERID_RecordedTVLibrary = &KNOWNFOLDERID{0x1a6fdba2, 0xf42d, 0x4358, [8]byte{0xa7, 0x98, 0xb7, 0x4d, 0x74, 0x59, 0x26, 0xc5}}
|
||||
FOLDERID_HomeGroup = &KNOWNFOLDERID{0x52528a6b, 0xb9e3, 0x4add, [8]byte{0xb6, 0x0d, 0x58, 0x8c, 0x2d, 0xba, 0x84, 0x2d}}
|
||||
FOLDERID_HomeGroupCurrentUser = &KNOWNFOLDERID{0x9b74b6a3, 0x0dfd, 0x4f11, [8]byte{0x9e, 0x78, 0x5f, 0x78, 0x00, 0xf2, 0xe7, 0x72}}
|
||||
FOLDERID_DeviceMetadataStore = &KNOWNFOLDERID{0x5ce4a5e9, 0xe4eb, 0x479d, [8]byte{0xb8, 0x9f, 0x13, 0x0c, 0x02, 0x88, 0x61, 0x55}}
|
||||
FOLDERID_Libraries = &KNOWNFOLDERID{0x1b3ea5dc, 0xb587, 0x4786, [8]byte{0xb4, 0xef, 0xbd, 0x1d, 0xc3, 0x32, 0xae, 0xae}}
|
||||
FOLDERID_PublicLibraries = &KNOWNFOLDERID{0x48daf80b, 0xe6cf, 0x4f4e, [8]byte{0xb8, 0x00, 0x0e, 0x69, 0xd8, 0x4e, 0xe3, 0x84}}
|
||||
FOLDERID_UserPinned = &KNOWNFOLDERID{0x9e3995ab, 0x1f9c, 0x4f13, [8]byte{0xb8, 0x27, 0x48, 0xb2, 0x4b, 0x6c, 0x71, 0x74}}
|
||||
FOLDERID_ImplicitAppShortcuts = &KNOWNFOLDERID{0xbcb5256f, 0x79f6, 0x4cee, [8]byte{0xb7, 0x25, 0xdc, 0x34, 0xe4, 0x02, 0xfd, 0x46}}
|
||||
FOLDERID_AccountPictures = &KNOWNFOLDERID{0x008ca0b1, 0x55b4, 0x4c56, [8]byte{0xb8, 0xa8, 0x4d, 0xe4, 0xb2, 0x99, 0xd3, 0xbe}}
|
||||
FOLDERID_PublicUserTiles = &KNOWNFOLDERID{0x0482af6c, 0x08f1, 0x4c34, [8]byte{0x8c, 0x90, 0xe1, 0x7e, 0xc9, 0x8b, 0x1e, 0x17}}
|
||||
FOLDERID_AppsFolder = &KNOWNFOLDERID{0x1e87508d, 0x89c2, 0x42f0, [8]byte{0x8a, 0x7e, 0x64, 0x5a, 0x0f, 0x50, 0xca, 0x58}}
|
||||
FOLDERID_StartMenuAllPrograms = &KNOWNFOLDERID{0xf26305ef, 0x6948, 0x40b9, [8]byte{0xb2, 0x55, 0x81, 0x45, 0x3d, 0x09, 0xc7, 0x85}}
|
||||
FOLDERID_CommonStartMenuPlaces = &KNOWNFOLDERID{0xa440879f, 0x87a0, 0x4f7d, [8]byte{0xb7, 0x00, 0x02, 0x07, 0xb9, 0x66, 0x19, 0x4a}}
|
||||
FOLDERID_ApplicationShortcuts = &KNOWNFOLDERID{0xa3918781, 0xe5f2, 0x4890, [8]byte{0xb3, 0xd9, 0xa7, 0xe5, 0x43, 0x32, 0x32, 0x8c}}
|
||||
FOLDERID_RoamingTiles = &KNOWNFOLDERID{0x00bcfc5a, 0xed94, 0x4e48, [8]byte{0x96, 0xa1, 0x3f, 0x62, 0x17, 0xf2, 0x19, 0x90}}
|
||||
FOLDERID_RoamedTileImages = &KNOWNFOLDERID{0xaaa8d5a5, 0xf1d6, 0x4259, [8]byte{0xba, 0xa8, 0x78, 0xe7, 0xef, 0x60, 0x83, 0x5e}}
|
||||
FOLDERID_Screenshots = &KNOWNFOLDERID{0xb7bede81, 0xdf94, 0x4682, [8]byte{0xa7, 0xd8, 0x57, 0xa5, 0x26, 0x20, 0xb8, 0x6f}}
|
||||
FOLDERID_CameraRoll = &KNOWNFOLDERID{0xab5fb87b, 0x7ce2, 0x4f83, [8]byte{0x91, 0x5d, 0x55, 0x08, 0x46, 0xc9, 0x53, 0x7b}}
|
||||
FOLDERID_SkyDrive = &KNOWNFOLDERID{0xa52bba46, 0xe9e1, 0x435f, [8]byte{0xb3, 0xd9, 0x28, 0xda, 0xa6, 0x48, 0xc0, 0xf6}}
|
||||
FOLDERID_OneDrive = &KNOWNFOLDERID{0xa52bba46, 0xe9e1, 0x435f, [8]byte{0xb3, 0xd9, 0x28, 0xda, 0xa6, 0x48, 0xc0, 0xf6}}
|
||||
FOLDERID_SkyDriveDocuments = &KNOWNFOLDERID{0x24d89e24, 0x2f19, 0x4534, [8]byte{0x9d, 0xde, 0x6a, 0x66, 0x71, 0xfb, 0xb8, 0xfe}}
|
||||
FOLDERID_SkyDrivePictures = &KNOWNFOLDERID{0x339719b5, 0x8c47, 0x4894, [8]byte{0x94, 0xc2, 0xd8, 0xf7, 0x7a, 0xdd, 0x44, 0xa6}}
|
||||
FOLDERID_SkyDriveMusic = &KNOWNFOLDERID{0xc3f2459e, 0x80d6, 0x45dc, [8]byte{0xbf, 0xef, 0x1f, 0x76, 0x9f, 0x2b, 0xe7, 0x30}}
|
||||
FOLDERID_SkyDriveCameraRoll = &KNOWNFOLDERID{0x767e6811, 0x49cb, 0x4273, [8]byte{0x87, 0xc2, 0x20, 0xf3, 0x55, 0xe1, 0x08, 0x5b}}
|
||||
FOLDERID_SearchHistory = &KNOWNFOLDERID{0x0d4c3db6, 0x03a3, 0x462f, [8]byte{0xa0, 0xe6, 0x08, 0x92, 0x4c, 0x41, 0xb5, 0xd4}}
|
||||
FOLDERID_SearchTemplates = &KNOWNFOLDERID{0x7e636bfe, 0xdfa9, 0x4d5e, [8]byte{0xb4, 0x56, 0xd7, 0xb3, 0x98, 0x51, 0xd8, 0xa9}}
|
||||
FOLDERID_CameraRollLibrary = &KNOWNFOLDERID{0x2b20df75, 0x1eda, 0x4039, [8]byte{0x80, 0x97, 0x38, 0x79, 0x82, 0x27, 0xd5, 0xb7}}
|
||||
FOLDERID_SavedPictures = &KNOWNFOLDERID{0x3b193882, 0xd3ad, 0x4eab, [8]byte{0x96, 0x5a, 0x69, 0x82, 0x9d, 0x1f, 0xb5, 0x9f}}
|
||||
FOLDERID_SavedPicturesLibrary = &KNOWNFOLDERID{0xe25b5812, 0xbe88, 0x4bd9, [8]byte{0x94, 0xb0, 0x29, 0x23, 0x34, 0x77, 0xb6, 0xc3}}
|
||||
FOLDERID_RetailDemo = &KNOWNFOLDERID{0x12d4c69e, 0x24ad, 0x4923, [8]byte{0xbe, 0x19, 0x31, 0x32, 0x1c, 0x43, 0xa7, 0x67}}
|
||||
FOLDERID_Device = &KNOWNFOLDERID{0x1c2ac1dc, 0x4358, 0x4b6c, [8]byte{0x97, 0x33, 0xaf, 0x21, 0x15, 0x65, 0x76, 0xf0}}
|
||||
FOLDERID_DevelopmentFiles = &KNOWNFOLDERID{0xdbe8e08e, 0x3053, 0x4bbc, [8]byte{0xb1, 0x83, 0x2a, 0x7b, 0x2b, 0x19, 0x1e, 0x59}}
|
||||
FOLDERID_Objects3D = &KNOWNFOLDERID{0x31c0dd25, 0x9439, 0x4f12, [8]byte{0xbf, 0x41, 0x7f, 0xf4, 0xed, 0xa3, 0x87, 0x22}}
|
||||
FOLDERID_AppCaptures = &KNOWNFOLDERID{0xedc0fe71, 0x98d8, 0x4f4a, [8]byte{0xb9, 0x20, 0xc8, 0xdc, 0x13, 0x3c, 0xb1, 0x65}}
|
||||
FOLDERID_LocalDocuments = &KNOWNFOLDERID{0xf42ee2d3, 0x909f, 0x4907, [8]byte{0x88, 0x71, 0x4c, 0x22, 0xfc, 0x0b, 0xf7, 0x56}}
|
||||
FOLDERID_LocalPictures = &KNOWNFOLDERID{0x0ddd015d, 0xb06c, 0x45d5, [8]byte{0x8c, 0x4c, 0xf5, 0x97, 0x13, 0x85, 0x46, 0x39}}
|
||||
FOLDERID_LocalVideos = &KNOWNFOLDERID{0x35286a68, 0x3c57, 0x41a1, [8]byte{0xbb, 0xb1, 0x0e, 0xae, 0x73, 0xd7, 0x6c, 0x95}}
|
||||
FOLDERID_LocalMusic = &KNOWNFOLDERID{0xa0c69a99, 0x21c8, 0x4671, [8]byte{0x87, 0x03, 0x79, 0x34, 0x16, 0x2f, 0xcf, 0x1d}}
|
||||
FOLDERID_LocalDownloads = &KNOWNFOLDERID{0x7d83ee9b, 0x2244, 0x4e70, [8]byte{0xb1, 0xf5, 0x53, 0x93, 0x04, 0x2a, 0xf1, 0xe4}}
|
||||
FOLDERID_RecordedCalls = &KNOWNFOLDERID{0x2f8b40c2, 0x83ed, 0x48ee, [8]byte{0xb3, 0x83, 0xa1, 0xf1, 0x57, 0xec, 0x6f, 0x9a}}
|
||||
FOLDERID_AllAppMods = &KNOWNFOLDERID{0x7ad67899, 0x66af, 0x43ba, [8]byte{0x91, 0x56, 0x6a, 0xad, 0x42, 0xe6, 0xc5, 0x96}}
|
||||
FOLDERID_CurrentAppMods = &KNOWNFOLDERID{0x3db40b20, 0x2a30, 0x4dbe, [8]byte{0x91, 0x7e, 0x77, 0x1d, 0xd2, 0x1d, 0xd0, 0x99}}
|
||||
FOLDERID_AppDataDesktop = &KNOWNFOLDERID{0xb2c5e279, 0x7add, 0x439f, [8]byte{0xb2, 0x8c, 0xc4, 0x1f, 0xe1, 0xbb, 0xf6, 0x72}}
|
||||
FOLDERID_AppDataDocuments = &KNOWNFOLDERID{0x7be16610, 0x1f7f, 0x44ac, [8]byte{0xbf, 0xf0, 0x83, 0xe1, 0x5f, 0x2f, 0xfc, 0xa1}}
|
||||
FOLDERID_AppDataFavorites = &KNOWNFOLDERID{0x7cfbefbc, 0xde1f, 0x45aa, [8]byte{0xb8, 0x43, 0xa5, 0x42, 0xac, 0x53, 0x6c, 0xc9}}
|
||||
FOLDERID_AppDataProgramData = &KNOWNFOLDERID{0x559d40a3, 0xa036, 0x40fa, [8]byte{0xaf, 0x61, 0x84, 0xcb, 0x43, 0x0a, 0x4d, 0x34}}
|
||||
)
|
||||
+4409
File diff suppressed because it is too large
Load Diff
Vendored
+5
-3
@@ -1,4 +1,6 @@
|
||||
# github.com/danieljoos/wincred v1.1.0
|
||||
# github.com/danieljoos/wincred v1.2.1
|
||||
## explicit; go 1.18
|
||||
github.com/danieljoos/wincred
|
||||
# golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4
|
||||
golang.org/x/sys/execabs
|
||||
# golang.org/x/sys v0.15.0
|
||||
## explicit; go 1.18
|
||||
golang.org/x/sys/windows
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build windows
|
||||
|
||||
package wincred
|
||||
|
||||
import (
|
||||
@@ -0,0 +1,291 @@
|
||||
//go:build windows
|
||||
|
||||
package wincred
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
func TestWinCredHelper(t *testing.T) {
|
||||
creds := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.docker.io:2376/v1",
|
||||
Username: "foobar",
|
||||
Secret: "foobarbaz",
|
||||
}
|
||||
creds1 := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.docker.io:2376/v2",
|
||||
Username: "foobarbaz",
|
||||
Secret: "foobar",
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
|
||||
// check for and remove remaining credentials from previous fail tests
|
||||
oldauths, err := helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for k, v := range oldauths {
|
||||
if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 {
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if strings.Compare(k, creds1.ServerURL) == 0 && strings.Compare(v, creds1.Username) == 0 {
|
||||
if err := helper.Delete(creds1.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recount for credentials
|
||||
oldauths, err = helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
username, secret, err := helper.Get(creds.ServerURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if username != "foobar" {
|
||||
t.Fatalf("expected %s, got %s\n", "foobar", username)
|
||||
}
|
||||
|
||||
if secret != "foobarbaz" {
|
||||
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
|
||||
}
|
||||
|
||||
auths, err := helper.List()
|
||||
if err != nil || len(auths)-len(oldauths) != 1 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
helper.Add(creds1)
|
||||
defer helper.Delete(creds1.ServerURL)
|
||||
newauths, err := helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(newauths)-len(auths) != 1 {
|
||||
if err == nil {
|
||||
t.Fatalf("Error: len(newauths): %d, len(auths): %d", len(newauths), len(auths))
|
||||
}
|
||||
t.Fatalf("Error: len(newauths): %d, len(auths): %d\n Error= %v", len(newauths), len(auths), err)
|
||||
}
|
||||
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWinCredHelperRetrieveAliases verifies that secrets can be accessed
|
||||
// through variations on the URL
|
||||
func TestWinCredHelperRetrieveAliases(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
{
|
||||
doc: "stored with port, retrieved without",
|
||||
storeURL: "https://foobar.docker.io:2376",
|
||||
readURL: "https://foobar.docker.io",
|
||||
},
|
||||
{
|
||||
doc: "stored as https, retrieved without scheme",
|
||||
storeURL: "https://foobar.docker.io",
|
||||
readURL: "foobar.docker.io",
|
||||
},
|
||||
{
|
||||
doc: "stored with path, retrieved without",
|
||||
storeURL: "https://foobar.docker.io/one/two",
|
||||
readURL: "https://foobar.docker.io",
|
||||
},
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
t.Cleanup(func() {
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Clean store before testing.
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err)
|
||||
}
|
||||
if _, _, err := helper.Get(tc.readURL); err != nil {
|
||||
t.Errorf("Error: failed to read secret for URL %q using %q", tc.storeURL, tc.readURL)
|
||||
}
|
||||
if err := helper.Delete(tc.storeURL); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWinCredHelperRetrieveStrict verifies that only matching secrets are
|
||||
// returned.
|
||||
func TestWinCredHelperRetrieveStrict(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
{
|
||||
doc: "stored as https, retrieved using http",
|
||||
storeURL: "https://foobar.docker.io:2376",
|
||||
readURL: "http://foobar.docker.io:2376",
|
||||
},
|
||||
{
|
||||
doc: "stored as http, retrieved using https",
|
||||
storeURL: "http://foobar.docker.io:2376",
|
||||
readURL: "https://foobar.docker.io:2376",
|
||||
},
|
||||
{
|
||||
// stored as http, retrieved without a scheme specified (hence, using the default https://)
|
||||
doc: "stored as http, retrieved without scheme",
|
||||
storeURL: "http://foobar.docker.io",
|
||||
readURL: "foobar.docker.io:5678",
|
||||
},
|
||||
{
|
||||
doc: "non-matching ports",
|
||||
storeURL: "https://foobar.docker.io:1234",
|
||||
readURL: "https://foobar.docker.io:5678",
|
||||
},
|
||||
// TODO: is this desired behavior? The other way round does work
|
||||
// {
|
||||
// doc: "non-matching ports (stored without port)",
|
||||
// storeURL: "https://foobar.docker.io",
|
||||
// readURL: "https://foobar.docker.io:5678",
|
||||
// },
|
||||
{
|
||||
doc: "non-matching paths",
|
||||
storeURL: "https://foobar.docker.io:1234/one/two",
|
||||
readURL: "https://foobar.docker.io:1234/five/six",
|
||||
},
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
t.Cleanup(func() {
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Clean store before testing.
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err)
|
||||
}
|
||||
if _, _, err := helper.Get(tc.readURL); err == nil {
|
||||
t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", tc.storeURL, tc.readURL)
|
||||
}
|
||||
if err := helper.Delete(tc.storeURL); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWinCredHelperStoreRetrieve verifies that secrets stored in the
|
||||
// the keychain can be read back using the URL that was used to store them.
|
||||
func TestWinCredHelperStoreRetrieve(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
}{
|
||||
{url: "foobar.docker.io"},
|
||||
{url: "foobar.docker.io:2376"},
|
||||
{url: "//foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376"},
|
||||
{url: "http://foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376/some/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar"},
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
t.Cleanup(func() {
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("cleanup: failed to delete '%s': %v", tc.url, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Clean store before testing.
|
||||
for _, tc := range tests {
|
||||
if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Errorf("prepare: failed to delete '%s': %v", tc.url, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we don't delete between individual tests here, to verify that
|
||||
// subsequent stores/overwrites don't affect storing / retrieving secrets.
|
||||
for i, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.url, func(t *testing.T) {
|
||||
c := &credentials.Credentials{
|
||||
ServerURL: tc.url,
|
||||
Username: fmt.Sprintf("user-%d", i),
|
||||
Secret: fmt.Sprintf("secret-%d", i),
|
||||
}
|
||||
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Fatalf("Error: failed to store secret for URL: %s: %s", tc.url, err)
|
||||
}
|
||||
user, secret, err := helper.Get(tc.url)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: failed to read secret for URL %q: %s", tc.url, err)
|
||||
}
|
||||
if user != c.Username {
|
||||
t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, tc.url)
|
||||
}
|
||||
if secret != c.Secret {
|
||||
t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, tc.url)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingCredentials(t *testing.T) {
|
||||
helper := Wincred{}
|
||||
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
|
||||
if !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
package wincred
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
func TestWinCredHelper(t *testing.T) {
|
||||
creds := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.docker.io:2376/v1",
|
||||
Username: "foobar",
|
||||
Secret: "foobarbaz",
|
||||
}
|
||||
creds1 := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.docker.io:2376/v2",
|
||||
Username: "foobarbaz",
|
||||
Secret: "foobar",
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
|
||||
// check for and remove remaining credentials from previous fail tests
|
||||
oldauths, err := helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for k, v := range oldauths {
|
||||
if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 {
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else if strings.Compare(k, creds1.ServerURL) == 0 && strings.Compare(v, creds1.Username) == 0 {
|
||||
if err := helper.Delete(creds1.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recount for credentials
|
||||
oldauths, err = helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
username, secret, err := helper.Get(creds.ServerURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if username != "foobar" {
|
||||
t.Fatalf("expected %s, got %s\n", "foobar", username)
|
||||
}
|
||||
|
||||
if secret != "foobarbaz" {
|
||||
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
|
||||
}
|
||||
|
||||
auths, err := helper.List()
|
||||
if err != nil || len(auths)-len(oldauths) != 1 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
helper.Add(creds1)
|
||||
defer helper.Delete(creds1.ServerURL)
|
||||
newauths, err := helper.List()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(newauths)-len(auths) != 1 {
|
||||
if err == nil {
|
||||
t.Fatalf("Error: len(newauths): %d, len(auths): %d", len(newauths), len(auths))
|
||||
}
|
||||
t.Fatalf("Error: len(newauths): %d, len(auths): %d\n Error= %v", len(newauths), len(auths), err)
|
||||
}
|
||||
|
||||
if err := helper.Delete(creds.ServerURL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWinCredHelperRetrieveAliases verifies that secrets can be accessed
|
||||
// through variations on the URL
|
||||
func TestWinCredHelperRetrieveAliases(t *testing.T) {
|
||||
tests := []struct {
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
// stored with port, retrieved without
|
||||
{"https://foobar.docker.io:2376", "https://foobar.docker.io"},
|
||||
|
||||
// stored as https, retrieved without scheme
|
||||
{"https://foobar.docker.io", "foobar.docker.io"},
|
||||
|
||||
// stored with path, retrieved without
|
||||
{"https://foobar.docker.io/one/two", "https://foobar.docker.io"},
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
|
||||
continue
|
||||
}
|
||||
if _, _, err := helper.Get(te.readURL); err != nil {
|
||||
t.Errorf("Error: failed to read secret for URL %q using %q", te.storeURL, te.readURL)
|
||||
}
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWinCredHelperRetrieveStrict verifies that only matching secrets are
|
||||
// returned.
|
||||
func TestWinCredHelperRetrieveStrict(t *testing.T) {
|
||||
tests := []struct {
|
||||
storeURL string
|
||||
readURL string
|
||||
}{
|
||||
// stored as https, retrieved using http
|
||||
{"https://foobar.docker.io:2376", "http://foobar.docker.io:2376"},
|
||||
|
||||
// stored as http, retrieved using https
|
||||
{"http://foobar.docker.io:2376", "https://foobar.docker.io:2376"},
|
||||
|
||||
// same: stored as http, retrieved without a scheme specified (hence, using the default https://)
|
||||
{"http://foobar.docker.io", "foobar.docker.io:5678"},
|
||||
|
||||
// non-matching ports
|
||||
{"https://foobar.docker.io:1234", "https://foobar.docker.io:5678"},
|
||||
|
||||
// non-matching ports TODO is this desired behavior? The other way round does work
|
||||
//{"https://foobar.docker.io", "https://foobar.docker.io:5678"},
|
||||
|
||||
// non-matching paths
|
||||
{"https://foobar.docker.io:1234/one/two", "https://foobar.docker.io:1234/five/six"},
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
|
||||
continue
|
||||
}
|
||||
if _, _, err := helper.Get(te.readURL); err == nil {
|
||||
t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", te.storeURL, te.readURL)
|
||||
}
|
||||
helper.Delete(te.storeURL)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWinCredHelperStoreRetrieve verifies that secrets stored in the
|
||||
// the keychain can be read back using the URL that was used to store them.
|
||||
func TestWinCredHelperStoreRetrieve(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
}{
|
||||
{url: "foobar.docker.io"},
|
||||
{url: "foobar.docker.io:2376"},
|
||||
{url: "//foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376"},
|
||||
{url: "http://foobar.docker.io:2376"},
|
||||
{url: "https://foobar.docker.io:2376/some/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path"},
|
||||
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar"},
|
||||
}
|
||||
|
||||
helper := Wincred{}
|
||||
defer func() {
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.url)
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean store before testing.
|
||||
for _, te := range tests {
|
||||
helper.Delete(te.url)
|
||||
}
|
||||
|
||||
// Note that we don't delete between individual tests here, to verify that
|
||||
// subsequent stores/overwrites don't affect storing / retrieving secrets.
|
||||
for i, te := range tests {
|
||||
c := &credentials.Credentials{
|
||||
ServerURL: te.url,
|
||||
Username: fmt.Sprintf("user-%d", i),
|
||||
Secret: fmt.Sprintf("secret-%d", i),
|
||||
}
|
||||
|
||||
if err := helper.Add(c); err != nil {
|
||||
t.Errorf("Error: failed to store secret for URL: %s: %s", te.url, err)
|
||||
continue
|
||||
}
|
||||
user, secret, err := helper.Get(te.url)
|
||||
if err != nil {
|
||||
t.Errorf("Error: failed to read secret for URL %q: %s", te.url, err)
|
||||
continue
|
||||
}
|
||||
if user != c.Username {
|
||||
t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, te.url)
|
||||
}
|
||||
if secret != c.Secret {
|
||||
t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, te.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingCredentials(t *testing.T) {
|
||||
helper := Wincred{}
|
||||
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
|
||||
if !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user