1
0
mirror of https://github.com/docker/docker-credential-helpers.git synced 2026-06-28 07:11:36 +05:30

Compare commits

...

182 Commits

Author SHA1 Message Date
Sebastiaan van Stijn 292722b503 Merge pull request #308 from thaJeztah/update_golang_1.21.6
update to go1.21.6
2024-01-10 18:01:53 +01:00
Sebastiaan van Stijn 979dcc4762 Merge pull request #309 from thaJeztah/update_golangci
Dockerfile: update golangci-lint to v1.55.2
2024-01-10 18:00:03 +01:00
Sebastiaan van Stijn f411a65c31 Dockerfile: update golangci-lint to v1.55.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-01-10 16:18:18 +01:00
Sebastiaan van Stijn 9629bd77ad update to go1.21.6
go1.20 is reaching EOL soon; let's update to the current version.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-01-10 14:42:35 +01:00
Sebastiaan van Stijn f642c26173 Merge pull request #306 from thaJeztah/err_checks
move trimming whitespace to error-check helpers
2024-01-10 14:40:13 +01:00
Sebastiaan van Stijn 8fc330691f Merge pull request #307 from thaJeztah/bump_wincred
vendor: github.com/danieljoos/wincred v1.2.1
2024-01-09 18:00:45 +01:00
Sebastiaan van Stijn 6a3e64c0b4 move trimming whitespace to error-check helpers
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-01-09 16:45:09 +01:00
Sebastiaan van Stijn 218f1787ac vendor: github.com/danieljoos/wincred v1.2.1
- Credential Helpers [v0.8.0](https://github.com/docker/docker-credential-helpers/releases/tag/v0.8.0)

full diff: https://github.com/danieljoos/wincred/compare/v1.2.0...v1.2.1

- Updated dependency golang.org/x/sys to version 0.15.0
- Updated dependency github.com/stretchr/testify to version 1.8.4
- Added error constant ErrBadUsername that can be used when dealing with domain password credentials.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-01-09 16:42:24 +01:00
Sebastiaan van Stijn 8396edb35f Merge pull request #297 from thaJeztah/update_go_1.20.6
update go to go1.20.6, debian bullseye
2023-07-17 02:13:55 +02:00
Sebastiaan van Stijn a3d1ffcdb2 update go to go1.20.6
go1.20.6 (released 2023-07-11) includes a security fix to the net/http package,
as well as bug fixes to the compiler, cgo, the cover tool, the go command,
the runtime, and the crypto/ecdsa, go/build, go/printer, net/mail, and text/template
packages. See the Go 1.20.6 milestone on our issue tracker for details.

https://github.com/golang/go/issues?q=milestone%3AGo1.20.6+label%3ACherryPickApproved

Full diff: https://github.com/golang/go/compare/go1.20.5...go1.20.6

These minor releases include 1 security fixes following the security policy:

net/http: insufficient sanitization of Host header

The HTTP/1 client did not fully validate the contents of the Host header.
A maliciously crafted Host header could inject additional headers or entire
requests. The HTTP/1 client now refuses to send requests containing an
invalid Request.Host or Request.URL.Host value.

Thanks to Bartek Nowotarski for reporting this issue.

Includes security fixes for [CVE-2023-29406 ][1] and Go issue https://go.dev/issue/60374

[1]: https://github.com/advisories/GHSA-f8f7-69v5-w4vx

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-07-17 00:40:48 +02:00
Sebastiaan van Stijn c03d56cfdc deb: update to golang bullseye
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-07-17 00:40:29 +02:00
Sebastiaan van Stijn 7f484550bc Merge pull request #294 from thaJeztah/use_designated_domains_step1
use designated domains in tests (RFC2606) (step 1)
2023-06-27 13:48:45 +02:00
Sebastiaan van Stijn a90e3fa153 secretservice: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn ffb3232f6c pass: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn 1050848357 client: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn 7d66ae02a6 osxkeychain: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn 13475b4387 credentials: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn 91af1de9af registryurl: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn 4977273244 Merge pull request #292 from thaJeztah/errors_improvements
credentials: improve errors and error-handling
2023-06-15 15:18:04 +02:00
Sebastiaan van Stijn a228f55121 credentials: implement errdefs types for typed errors
This allows for checking the error-type returned to be matched with
the errdefs utilities.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-15 14:59:44 +02:00
Sebastiaan van Stijn dbfb389f83 credentials: use errors.As() to match error-types
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-15 14:59:35 +02:00
Sebastiaan van Stijn bd83e02ad0 Merge pull request #293 from thaJeztah/update_go_1.20.5
update go to go1.20.5
2023-06-15 14:57:52 +02:00
Sebastiaan van Stijn a6e03c60ab update go to go1.20.5
go1.20.5 (released 2023-06-06) includes four security fixes to the cmd/go and
runtime packages, as well as bug fixes to the compiler, the go command, the
runtime, and the crypto/rsa, net, and os packages. See the Go 1.20.5 milestone
on our issue tracker for details:

https://github.com/golang/go/issues?q=milestone%3AGo1.20.5+label%3ACherryPickApproved

full diff: https://github.com/golang/go/compare/go1.20.4...go1.20.5

These minor releases include 3 security fixes following the security policy:

- cmd/go: cgo code injection
  The go command may generate unexpected code at build time when using cgo. This
  may result in unexpected behavior when running a go program which uses cgo.

  This may occur when running an untrusted module which contains directories with
  newline characters in their names. Modules which are retrieved using the go command,
  i.e. via "go get", are not affected (modules retrieved using GOPATH-mode, i.e.
  GO111MODULE=off, may be affected).

  Thanks to Juho Nurminen of Mattermost for reporting this issue.

  This is CVE-2023-29402 and Go issue https://go.dev/issue/60167.

- runtime: unexpected behavior of setuid/setgid binaries

  The Go runtime didn't act any differently when a binary had the setuid/setgid
  bit set. On Unix platforms, if a setuid/setgid binary was executed with standard
  I/O file descriptors closed, opening any files could result in unexpected
  content being read/written with elevated prilieges. Similarly if a setuid/setgid
  program was terminated, either via panic or signal, it could leak the contents
  of its registers.

  Thanks to Vincent Dehors from Synacktiv for reporting this issue.

  This is CVE-2023-29403 and Go issue https://go.dev/issue/60272.

- cmd/go: improper sanitization of LDFLAGS

  The go command may execute arbitrary code at build time when using cgo. This may
  occur when running "go get" on a malicious module, or when running any other
  command which builds untrusted code. This is can by triggered by linker flags,
  specified via a "#cgo LDFLAGS" directive.

  Thanks to Juho Nurminen of Mattermost for reporting this issue.

  This is CVE-2023-29404 and CVE-2023-29405 and Go issues https://go.dev/issue/60305 and https://go.dev/issue/60306.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-15 12:11:09 +02:00
Sebastiaan van Stijn c842499594 Merge pull request #291 from thaJeztah/test_nits
credentials: fix minor nits in tests
2023-05-30 15:21:24 +02:00
Sebastiaan van Stijn c1c7dd7011 credentials: fix minor nits in tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-30 11:02:44 +02:00
Sebastiaan van Stijn 83d38ea5e6 Merge pull request #289 from crazy-max/build-constraint
chore: use go build constraint
2023-05-29 17:54:04 +02:00
CrazyMax 72391b37df pass: properly handle errors in tests
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:49 +02:00
CrazyMax d0668939bb pass: exclude tests on windows
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:49 +02:00
CrazyMax a51d46e82c ci: set gpg key trust level with import-gpg action
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:48 +02:00
CrazyMax ea29253d2a ci: install pass on macOS for testing
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:48 +02:00
CrazyMax 90bf5da1c9 wincred: use go build constraint
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:48 +02:00
CrazyMax 6a2f53622b secretservice: use go build constraint
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:48 +02:00
CrazyMax 9f5511c8d5 osxkeychain: use go build constraint
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:48 +02:00
CrazyMax c740b990c2 Merge pull request #287 from thaJeztah/pass_no_interpolate
pass: fix interpolation of $PASSWORD_STORE_DIR, and use os.UserHomeDir()
2023-05-29 12:02:13 +02:00
Sebastiaan van Stijn 372315b138 pass: make home-dir resolution platform agnostic
Use stdlib's os.UserHomeDir() instead of depending only on $HOME. Note that
this does not yet does nss lookups for situations where $HOME / $USERPROFILE
is not set.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-29 08:54:49 +02:00
Sebastiaan van Stijn c8c415f9f7 pass: fix interpolation of $PASSWORD_STORE_DIR
commit a13ff50017 simplified the handling of
env-vars in getPassDir(), but moved interpolation of env-vars to the end
of the function.

As a result, a custom path passed through `$PASSWORD_STORE_DIR` would now
be interpolated, instead of taken as-is. For example;

    PASSWORD_STORE_DIR=$PWD/world

Would now interpolate `$PWD`, instead of using a literal `$PWD`.

This patch changes the logic to only expand env-vars for the default location.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 18:42:06 +02:00
Sebastiaan van Stijn a652f8e7e6 Merge pull request #286 from crazy-max/update-readme
readme: install emulators when building with docker
2023-05-28 18:05:09 +02:00
Sebastiaan van Stijn 2860ca4b4d Merge pull request #283 from crazy-max/fix-osx-min-version
osxkeychain: match min macos version for xx
2023-05-28 18:00:17 +02:00
CrazyMax 2103f1bcee readme: install emulators when building with docker
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-28 17:40:41 +02:00
Sebastiaan van Stijn bdd92dd0d3 Merge pull request #284 from thaJeztah/various_cleanups
Assorted improvements, and add  "--version, -v", and "--help, -h" flags
2023-05-28 15:56:28 +02:00
CrazyMax 5944f8a485 osxkeychain: match min macos version for xx
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-28 15:55:56 +02:00
CrazyMax 0b9180511f Merge pull request #285 from crazy-max/test-target
chore: use same target for sandboxed and native tests
2023-05-28 15:20:22 +02:00
CrazyMax d440e5916e chore: use same target for sandboxed and native tests
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-28 15:13:50 +02:00
Sebastiaan van Stijn 129017a3cd credentials: define consts for supported actions (sub-commands)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 13:53:34 +02:00
Sebastiaan van Stijn 99079cafd2 credentials: Serve(): implement "--version, -v", and "--help, -h" flags
As recommended in the GNU documentation;

- https://www.gnu.org/prep/standards/standards.html#g_t_002d_002dversion
- https://www.gnu.org/prep/standards/standards.html#g_t_002d_002dhelp

With this patch:

    $ docker-credential-osxkeychain --version
    docker-credential-osxkeychain (github.com/docker/docker-credential-helpers) v0.7.0-51-g26c426e.m

    $ docker-credential-osxkeychain -v
    docker-credential-osxkeychain (github.com/docker/docker-credential-helpers) v0.7.0-51-g26c426e.m

    $ docker-credential-osxkeychain --help
    Usage: docker-credential-osxkeychain <store|get|erase|list|version>

    $ docker-credential-osxkeychain -h
    Usage: docker-credential-osxkeychain <store|get|erase|list|version>

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 13:53:34 +02:00
Sebastiaan van Stijn ae1d1ec013 credentials: HandleCommand(): improve error for unknown command/action
- renamed the "key" variable, which was slightly confusing
- include the name of the binary in the error

Before this change:

    docker-credential-osxkeychain nosuchaction
    Unknown credential action `nosuchaction`

After this change:

    docker-credential-osxkeychain nosuchaction
    docker-credential-osxkeychain: unknown action: nosuchaction

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 13:53:34 +02:00
Sebastiaan van Stijn db0ac44c97 credentials: Serve(): simplify error-handling logic
Don't use an err if we can print the error immediately :)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 13:53:34 +02:00
Sebastiaan van Stijn 0dbcdb66a7 credentials: Serve(): use "Name instead of "os.Args[0]" for usage output
GNU guidelines describes; https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion

    The program’s name should be a constant string; don’t compute it from argv[0].
    The idea is to state the standard or canonical name for the program, not its
    file name.

Although the above recommendation is for `--version` output, it probably makes
sense to do the same for the "usage" output.

Before this change:

    /usr/local/bin/docker-credential-osxkeychain invalid command
    Usage: /usr/local/bin/docker-credential-osxkeychain <store|get|erase|list|version>

    /Applications/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain invalid command
    Usage: /Applications/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain <store|get|erase|list|version>

With this patch:

    /usr/local/bin/docker-credential-osxkeychain invalid command
    Usage: docker-credential-osxkeychain <store|get|erase|list|version>

    /Applications/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain invalid command
    Usage: docker-credential-osxkeychain <store|get|erase|list|version>

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 13:53:23 +02:00
Sebastiaan van Stijn c324fe0a6f credentials: Get(): remove intermediate variable
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 12:38:05 +02:00
Sebastiaan van Stijn 19557f8fff fix some errCheck warnings, and update examples
- Explicitly suppress some unhandled errors
- Use "pass" credentials helper in examples, which is available
  on more platforms than "secretservice" (only supporte on Linux)
- Update domain and username in examples.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 12:33:58 +02:00
Sebastiaan van Stijn 94483d2d23 godoc: credentials helper -> credentials-helper
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 12:13:49 +02:00
Sebastiaan van Stijn 4c9fc240ed client: use os/exec/Cmd.Environ() instead of os.Environ()
Don't set Env if not set; the default is already handled if it's nil; from
the documentation: https://pkg.go.dev/os/exec@go1.20.4#Cmd.Env

    // If Env is nil, the new process uses the current process's
    // environment.

Use `os/exec/Cmd.Environ()` instead of `os.Environ()`, which was added in
go1.19, and handles additional environment variables, such as `PWD` on POSIX
systems, and `SYSTEMROOT` on Windows. https://pkg.go.dev/os/exec@go1.20.4#Cmd.Environ

Also remove a redundant `fmt.Sprintf()`, as we're only concatenating strings.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 12:11:06 +02:00
Sebastiaan van Stijn f8e94d91c0 Merge pull request #275 from thaJeztah/subtests
rewrite tests to use sub-tests, improve error-handling in tests, and use t.Cleanup()
2023-05-27 19:18:19 +02:00
Sebastiaan van Stijn 6f4e3abfc8 Merge pull request #281 from thaJeztah/bump_go_version
update go to go1.20.4
2023-05-27 19:17:16 +02:00
Sebastiaan van Stijn ec695cee3c Merge pull request #279 from crazy-max/win-arm64
windows/arm64 support for wincred
2023-05-27 19:00:36 +02:00
Sebastiaan van Stijn fa89a70db3 update go to go1.20.4
go1.20.4 (released 2023-05-02) includes three security fixes to the html/template
package, as well as bug fixes to the compiler, the runtime, and the crypto/subtle,
crypto/tls, net/http, and syscall packages. See the Go 1.20.4 milestone on our
issue tracker for details:

https://github.com/golang/go/issues?q=milestone%3AGo1.20.4+label%3ACherryPickApproved

release notes: https://go.dev/doc/devel/release#go1.20.4
full diff: https://github.com/golang/go/compare/go1.20.3...go1.20.4

from the announcement:

> These minor releases include 3 security fixes following the security policy:
>
> - html/template: improper sanitization of CSS values
>
>   Angle brackets (`<>`) were not considered dangerous characters when inserted
>   into CSS contexts. Templates containing multiple actions separated by a '/'
>   character could result in unexpectedly closing the CSS context and allowing
>   for injection of unexpected HMTL, if executed with untrusted input.
>
>   Thanks to Juho Nurminen of Mattermost for reporting this issue.
>
>   This is CVE-2023-24539 and Go issue https://go.dev/issue/59720.
>
> - html/template: improper handling of JavaScript whitespace
>
>   Not all valid JavaScript whitespace characters were considered to be
>   whitespace. Templates containing whitespace characters outside of the character
>   set "\t\n\f\r\u0020\u2028\u2029" in JavaScript contexts that also contain
>   actions may not be properly sanitized during execution.
>
>   Thanks to Juho Nurminen of Mattermost for reporting this issue.
>
>   This is CVE-2023-24540 and Go issue https://go.dev/issue/59721.
>
> - html/template: improper handling of empty HTML attributes
>
>   Templates containing actions in unquoted HTML attributes (e.g. "attr={{.}}")
>   executed with empty input could result in output that would have unexpected
>   results when parsed due to HTML normalization rules. This may allow injection
>   of arbitrary attributes into tags.
>
>   Thanks to Juho Nurminen of Mattermost for reporting this issue.
>
>   This is CVE-2023-29400 and Go issue https://go.dev/issue/59722.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 18:57:55 +02:00
Sebastiaan van Stijn 14d46ffd7e credentials: don't fail tests early, and use consts
- use consts for fixed values in tests
- don't fail tests early if there's more we can test

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:13 +02:00
Sebastiaan van Stijn c20f883316 client: don't use un-keyed literals in tests, and don't fail early
- don't fail tests early if there's more we can test
- don't user un-keyed literals in tests

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:13 +02:00
Sebastiaan van Stijn 7a60d70114 wincred: use sub-tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:13 +02:00
Sebastiaan van Stijn 814dbb3b5a wincred: use t.Cleanup(), and don't ignore errors
Make sure we don't drop errors when cleaning up state before/after
tests.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:13 +02:00
Sebastiaan van Stijn ed91395f20 wincred: don't use un-keyed literals in test-tables
Also moving the comments into the test-tables.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:12 +02:00
Sebastiaan van Stijn 5c5b09e7f8 osxkeychain: use sub-tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:12 +02:00
Sebastiaan van Stijn 8282d3336a osxkeychain: use t.Cleanup(), and don't ignore errors
Make sure we don't drop errors when cleaning up state before/after
tests.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:12 +02:00
Sebastiaan van Stijn a7ff1c7d16 osxkeychain: don't use un-keyed literals in test-tables
Also moving the comments into the test-tables.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:12 +02:00
Sebastiaan van Stijn 4a8c2d1d81 registryurl: use sub-tests
Also reformat the test-table to easier see what fields are used for
each test in the table.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:12 +02:00
Sebastiaan van Stijn 810dcd4ed5 use "tc" for test-cases
Mostly for my own sanity; just about every repository we have
started to converge to using "tc" as variable name for this, so
updating this repository as well to help reduce cognitive load.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:09 +02:00
Sebastiaan van Stijn f09e79d741 Merge pull request #278 from thaJeztah/osxkeychain_typed_error
osxkeychain: Delete(): return typed errors
2023-05-27 17:13:23 +02:00
Sebastiaan van Stijn b21b69c8ee osxkeychain: Delete(): return typed errors
This allows a Delete for non-existing credentials to be handled.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 16:54:37 +02:00
Sebastiaan van Stijn 7f00c5c8bd osxkeychain: use a switch for handling errors
Makes the error-handling slightly cleaner.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 16:54:37 +02:00
Sebastiaan van Stijn 1ed95cb020 osxkeychain: explicitly ignore error
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 16:54:32 +02:00
CrazyMax 88cb947f19 windows/arm64 support for wincred
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-27 16:46:54 +02:00
Sebastiaan van Stijn 9ff5b6126b Merge pull request #276 from crazy-max/ci-bump-os
ci: bump runners to ubuntu-22.04
2023-05-27 16:07:51 +02:00
CrazyMax 62d8c84526 ci: add ubuntu-22.04 to test matrix
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-27 15:58:02 +02:00
CrazyMax 2749e559d9 ci: bump runners to ubuntu-22.04
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-27 15:51:14 +02:00
Sebastiaan van Stijn 4ede49ca78 Merge pull request #273 from thaJeztah/gofumpt
format code with gofumpt
2023-05-27 13:44:47 +02:00
Sebastiaan van Stijn a001d639ae Merge pull request #272 from thaJeztah/add_project_files
Add CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md
2023-05-27 13:30:15 +02:00
Sebastiaan van Stijn 9817a23b17 format code with gofumpt
Doing a one-pass of formatting the code with gofumpt, which provides a
superset of gofmt.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 13:01:49 +02:00
Sebastiaan van Stijn 11e6d3772c Merge pull request #271 from thaJeztah/bump_wincred
vendor: github.com/danieljoos/wincred v1.2.0
2023-05-27 12:42:22 +02:00
Sebastiaan van Stijn a649a36b27 project: add CONTRIBUTING.md
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 12:38:04 +02:00
Sebastiaan van Stijn 9ddc7c7f86 project: add CODE_OF_CONDUCT.md
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 12:37:46 +02:00
Sebastiaan van Stijn e6a96be547 project: Add SECURITY.md
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 12:37:21 +02:00
Sebastiaan van Stijn 069ceb8b56 vendor: github.com/danieljoos/wincred v1.2.0
full diff: https://github.com/danieljoos/wincred/compare/v1.1.2...v1.2.0

- Calling SyscallN directly when dealing with pointer-pointers to try to address
  "Unexpected (nil, nil) result from wincred.GetGenericCredential".
- Bumped required Go version to 1.18.
- Bumped dependency to golang.org/x/sys to version 0.8.0.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 11:52:12 +02:00
Sebastiaan van Stijn 7ce5629658 Merge pull request #270 from thaJeztah/remove_execabs
remove uses of golang.org/x/sys/execabs
2023-05-27 11:47:10 +02:00
Sebastiaan van Stijn 37c4a6b158 remove uses of golang.org/x/sys/execabs
the "golang.org/x/sys/execabs" package was introduced to address a security
issue on Windows, and changing the default behavior of os/exec was considered
a breaking change. go1.19 applied the behavior that was previously implemented
in the execabs package;

from the release notes: https://go.dev/doc/go1.19#os-exec-path

> Command and LookPath no longer allow results from a PATH search to be found
> relative to the current directory. This removes a common source of security
> problems but may also break existing programs that depend on using, say,
> exec.Command("prog") to run a binary named prog (or, on Windows, prog.exe)
> in the current directory. See the os/exec package documentation for information
> about how best to update such programs.
>
> On Windows, Command and LookPath now respect the NoDefaultCurrentDirectoryInExePath
> environment variable, making it possible to disable the default implicit search
> of “.” in PATH lookups on Windows systems.

With those changes, we no longer need to use the execabs package, and we can
switch back to os/exec.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-26 02:11:17 +02:00
Sebastiaan van Stijn da93839996 Merge pull request #263 from crazy-max/update-go
update go to 1.20
2023-04-16 12:34:50 +02:00
Sebastiaan van Stijn da7f673a5e Merge pull request #265 from crazy-max/bump-golang-sys
Bump golang.org/x/sys to v0.7.0
2023-04-16 12:29:22 +02:00
CrazyMax d5c91a2f56 remove deprecated golangci-lint linters
Linters 'deadcode', 'structcheck', 'varcheck' are deprecated
since v1.49.0 and had been replaced by 'unused'.

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-04-16 12:29:03 +02:00
CrazyMax e9656dd67f update golangci-lint to v1.51.1
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-04-16 12:29:03 +02:00
CrazyMax 52f0cb6de6 update go to 1.20
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-04-16 12:29:03 +02:00
Sebastiaan van Stijn bde49b32dd Merge pull request #264 from crazy-max/update-xx
Dockerfile: update xx to 1.2.1
2023-04-16 12:26:14 +02:00
CrazyMax b3ec48855b Bump golang.org/x/sys to v0.7.0
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-04-16 12:07:42 +02:00
CrazyMax 0db06514b9 update xx to 1.2.1
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-04-16 11:24:27 +02:00
CrazyMax d7835111fd Merge pull request #252 from okrc/patch-1
Fix build status badge being broken
2023-01-30 13:38:11 +01:00
okrc be409725f3 Fix build status badge being broken
Signed-off-by: okrc <okrc@hexo.dev>
2023-01-30 12:19:07 +01:00
Sebastiaan van Stijn 20454add32 Merge pull request #253 from crazy-max/fix-dockerfile
Dockerfile: fix vendor validation
2023-01-30 12:02:01 +01:00
CrazyMax 5674caebaf Dockerfile: fix vendor validation
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-01-22 21:47:18 +01:00
Sebastiaan van Stijn ac5992b5f4 Merge pull request #239 from crazy-max/debian-base
Dockerfile: debian based build
2022-08-31 09:33:52 +02:00
CrazyMax 205638eeb3 ci: add concurrency check
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-30 13:09:09 +02:00
CrazyMax 29c449aea8 Dockerfile: debian based build
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-30 13:09:08 +02:00
Sebastiaan van Stijn 2f246b8570 Merge pull request #221 from nicks/nicks/pass
pass: return an error when a cred doesn't exist
2022-08-28 21:33:45 +02:00
CrazyMax 0ab1dd03b3 Merge pull request #238 from thaJeztah/bump_golang_sys
vendor: golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
2022-08-28 21:15:35 +02:00
Sebastiaan van Stijn 3526ac5303 vendor: golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
update to latest version

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-08-28 21:06:44 +02:00
Sebastiaan van Stijn 7c04fb1f9c Merge pull request #237 from crazy-max/release
Release target with checksums
2022-08-28 20:58:51 +02:00
CrazyMax d4880decb1 remove CHANGELOG
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-28 20:53:51 +02:00
CrazyMax 214ecc1238 create GitHub Release on push tag with artifacts
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-28 20:53:51 +02:00
CrazyMax 056f8792b7 release target with checksums
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-28 20:53:51 +02:00
Sebastiaan van Stijn da1d534b46 Merge pull request #234 from crazy-max/versioning
Set version and revision at linked time
2022-08-27 00:37:28 +02:00
CrazyMax a2d8aac9dd ci: add build-deb job
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-27 00:32:19 +02:00
CrazyMax 48ab0d84e8 Fix deb package
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-27 00:32:19 +02:00
CrazyMax e50298d973 Makefile: mutualize local and Dockerfile build opts
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-27 00:32:19 +02:00
CrazyMax 2c31fa46db ci: fetch depth 0 at build time for proper versioning
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-25 14:50:26 +02:00
CrazyMax a37d38a864 Set version and revision at linked time
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-25 14:50:18 +02:00
Sebastiaan van Stijn 51249117fa Merge pull request #236 from ZauberNerd/relative-paths-makefile
Use relative paths in Makefile for "go build"
2022-08-25 12:34:43 +02:00
Björn Brauer 3289c31a02 Use relative paths in Makefile for "go build"
https://github.com/docker/docker-credential-helpers/pull/232#issuecomment-1226943341

Fixes #232

Signed-off-by: Björn Brauer <bjoern.brauer@new-work.se>
2022-08-25 10:02:20 +00:00
Sebastiaan van Stijn ebd9dc6c79 Merge pull request #169 from ellsclytn/pass-macos-and-linux
Allow pass helper to be built for macOS
2022-08-21 16:24:33 +02:00
Sebastiaan van Stijn cc29c66bcc Dockerfile: build pass on macOS
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-08-21 16:19:22 +02:00
Ellis Clayton dd465ef1f3 Allow pass helper to be built for macOS
Pass is described as "The Standard Unix Password Manager", and so it can
easily run on more than just Linux. Namely, it's supported on macOS. The
CLI is identical to the Linux build, which means the Linux helper code
for Pass is fully applicable toward the macOS build - a couple of
renames being the only needed thing, purely for semantic correctness.

Signed-off-by: Ellis Clayton <ellis@ellis.codes>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-08-21 16:18:19 +02:00
Sebastiaan van Stijn a9d6be0a41 Merge pull request #233 from thaJeztah/update_readme_instructions
Update local build instructions in README
2022-08-21 16:16:39 +02:00
Sebastiaan van Stijn becf2f2a95 Update local build instructions in README
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-08-21 16:10:50 +02:00
Sebastiaan van Stijn 6dfcfc15aa Merge pull request #232 from thaJeztah/simplify_build
simplify "go build" commands
2022-08-21 15:43:02 +02:00
Sebastiaan van Stijn 5a61a5bc61 Merge pull request #231 from crazy-max/lint
lint gha job and remove travis
2022-08-21 15:42:07 +02:00
CrazyMax f522992580 remove travis
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-21 15:33:57 +02:00
CrazyMax 9ec715d8ed ci: init gpg key and pass for tests
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-21 15:33:57 +02:00
CrazyMax 9582eb6661 lint gha job
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-21 15:33:56 +02:00
Sebastiaan van Stijn e847de4b42 simplify "go build" commands
There's no need to specify the file to build, as we're building the
"main" packages as a whole.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-08-21 15:30:16 +02:00
Sebastiaan van Stijn 5302241995 Merge pull request #230 from crazy-max/vendor-license
vendor validation
2022-08-20 22:40:10 +02:00
CrazyMax e5695df009 vendor update and validation
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-20 21:49:52 +02:00
Sebastiaan van Stijn acd90ea9da Merge pull request #227 from crazy-max/ppc64le-s390x
support linux/ppc64le and linux/s390x cross build
2022-08-20 21:11:04 +02:00
CrazyMax 018b71b3c3 support linux/ppc64le and linux/s390x cross build
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-20 21:03:51 +02:00
Sebastiaan van Stijn 662759718c Merge pull request #226 from thaJeztah/bump_go_1.18.5
update to go 1.18.5
2022-08-20 20:59:20 +02:00
Sebastiaan van Stijn fd0197473f update to go 1.18.5
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-08-20 20:49:39 +02:00
Sebastiaan van Stijn ab7fd12c67 Merge pull request #224 from crazy-max/ci-test
Test stage and ci jobs
2022-08-20 20:49:28 +02:00
CrazyMax 6f1a1a10c1 update README with new build instructions and badges
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-20 20:44:51 +02:00
CrazyMax 667e4702b8 ci: add test jobs
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-20 20:44:50 +02:00
Sebastiaan van Stijn 8fee1128b6 Merge pull request #225 from thaJeztah/drop_go1.8
registryurl: remove fallback code for go < 1.8
2022-08-20 20:35:09 +02:00
Sebastiaan van Stijn 70f476531f registryurl: deprecate GetHostname(), GetPort()
These were just wrappers for url.Hostname() and url.Port()

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-08-20 20:25:03 +02:00
Sebastiaan van Stijn ebbaee6ed4 registryurl: remove fallback code for go < 1.8
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-08-20 20:18:03 +02:00
CrazyMax e7e9118856 Dockerfile: add test stage
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-20 18:55:43 +02:00
Sebastiaan van Stijn 0e5dbc62ad Merge pull request #219 from crazy-max/dockerfile
Dockerfile for cross compilation
2022-08-20 17:38:04 +02:00
CrazyMax a251a3e4c5 Remove unused Jenkinsfile
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-20 17:32:01 +02:00
CrazyMax 4f1c080d3b Basic GHA workflow for cross compilation
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-20 17:32:01 +02:00
CrazyMax 17020d97fe Dockerfile for cross compilation
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2022-08-20 17:32:00 +02:00
Nick Santos 2fc2313bb1 pass: return an error when a cred doesn't exist
fixes https://github.com/docker/docker-credential-helpers/issues/174

Signed-off-by: Nick Santos <nick.santos@docker.com>
2022-07-27 09:28:13 -04:00
Sebastiaan van Stijn e595cd6946 Merge pull request #208 from crazy-max/wincred
Bump github.com/danieljoos/wincred to v1.1.2
2021-09-14 16:41:53 +02:00
Mathieu Champlon a4e88fa4cf Merge pull request #211 from alakesh/master
Fix return values in List() in secretservice_linux.go
2021-09-08 06:52:50 +02:00
Alakesh Haloi 48bfed47cc Fix return values in List() in secretservice_linux.go
In case of error, return nil for expected map along with error

Signed-off-by: Alakesh Haloi <alakeshh@amazon.com>
2021-09-07 17:14:20 -07:00
Emmanuel Briney 6e2a858a7a Merge pull request #191 from QiWang19/list-error
List wraps the error from secretservice_linux.c.
2021-08-31 11:53:41 +02:00
Qi Wang 8086f00d7d List wraps the error from secretservice_linux.c.
List wraps the error from secretservice_linux.c so the List can give the caller more useful error message for diagnosing.

Signed-off-by: Qi Wang <qiwan@redhat.com>
2021-08-28 20:44:52 -04:00
CrazyMax 16c3805fc7 Bump github.com/danieljoos/wincred to v1.1.2
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-08-20 13:07:42 +02:00
David Scott fc9290adbc Merge pull request #203 from ebriney/bump_to_v0.6.4
Bump to 0.6.4
2021-06-02 11:34:05 +01:00
Emmanuel Briney 8369960895 Merge pull request #205 from thaJeztah/fix_ci2
travis: temporarily disabling ppc64le, and fix some linting issues
2021-06-01 12:36:26 +02:00
Sebastiaan van Stijn e4a625d24b travis: temporarily disable ppc
The ppc machines use systemd, so starting xvfb  doesnt work

    +sh -e /etc/init.d/xvfb start
    sh: 0: Can't open /etc/init.d/xvfb

Temporarily disabling it for now

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-06-01 10:36:09 +02:00
Sebastiaan van Stijn f552261f32 travis: fix selection of ppc64le
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-06-01 10:22:13 +02:00
Sebastiaan van Stijn 7a2694fc98 fix some linting issues
/go/src/github.com/docker/docker-credential-helper/pass/pass.go:1:1: package comment should be of the form "Package pass ..."
    /go/src/github.com/docker/docker-credential-helper/pass/pass.go:22:7: don't use ALL_CAPS in Go names; use CamelCase
    /go/src/github.com/docker/docker-credential-helper/pass/pass.go:22:7: exported const PASS_FOLDER should have comment or be unexported
    /go/src/github.com/docker/docker-credential-helper/pass/pass.go:82:1: receiver name h should be consistent with previous receiver name p for Pass
    /go/src/github.com/docker/docker-credential-helper/pass/pass.go:94:1: receiver name h should be consistent with previous receiver name p for Pass
    /go/src/github.com/docker/docker-credential-helper/pass/pass.go:131:1: receiver name h should be consistent with previous receiver name p for Pass
    /go/src/github.com/docker/docker-credential-helper/pass/pass.go:161:1: receiver name h should be consistent with previous receiver name p for Pass
    /go/src/github.com/docker/docker-credential-helper/registryurl/url_go18.go:9:1: exported function GetHostname should have comment or be unexported
    /go/src/github.com/docker/docker-credential-helper/registryurl/url_go18.go:13:1: exported function GetPort should have comment or be unexported
    /go/src/github.com/docker/docker-credential-helper/secretservice/secretservice_linux_test.go:22:2: don't use underscores in Go names; var old_auths should be oldAuths
    /go/src/github.com/docker/docker-credential-helper/secretservice/secretservice_linux_test.go:64:2: don't use underscores in Go names; var new_auths should be newAuths

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-06-01 10:22:11 +02:00
Emmanuel Briney 4b9fe97b79 Merge pull request #187 from espindola/openbsd
Enable pass on non-linux systems
2021-05-31 14:23:42 +02:00
ebriney 0232333efa Bump to 0.6.4
Signed-off-by: ebriney <emmanuel.briney@docker.com>
2021-05-27 17:11:30 +02:00
Tibor Vass 38bea2ce27 Merge pull request #195 from tiborvass/execabs
Use golang.org/x/sys/execabs
2021-01-25 09:24:08 -08:00
Tibor Vass c9a35c136e Merge pull request #168 from thaJeztah/go_mod
Add go.mod, and vendor wincred v1.1.0
2021-01-25 09:18:51 -08:00
Tibor Vass 431b64c703 Use golang.org/x/sys/execabs
Signed-off-by: Tibor Vass <tibor@docker.com>
2021-01-21 20:14:54 +00:00
Tibor Vass 951b97d7f5 vendor: update wincred to v1.1.0
Signed-off-by: Tibor Vass <tibor@docker.com>
2021-01-21 20:10:42 +00:00
Sebastiaan van Stijn f7d32862eb Add go.mod, and vendor wincred v1.0.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-01-21 20:08:59 +00:00
Sebastiaan van Stijn 699f1d6790 Merge pull request #193 from tonistiigi/osx-message
osxkeychain: improve error message for non-interactive sessions
2021-01-20 10:56:22 +01:00
Tonis Tiigi f4b8a8531e osxkeychain: improve error message for non-interactive sessions
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2021-01-07 11:38:06 -08:00
Rafael Ávila de Espíndola f76f0b3f33 Enable pass on non-linux systems
There is nothing linux specific about pass. In particular, I use it in
openbsd.

Signed-off-by: Rafael Ávila de Espíndola <rafael@espindo.la>
2020-09-14 20:54:21 -07:00
Guillaume Rose f78081d1f7 Merge pull request #161 from StefanScherer/update-windows-build-2019-pfx
[DESKTOP-1469] Update Windows Authenticode certificate
2019-07-20 08:39:34 +02:00
Stefan Scherer 4e7d4a98a4 Update Windows Authenticode pfx
Signed-off-by: Stefan Scherer <stefan.scherer@docker.com>
2019-07-18 08:37:50 +02:00
Guillaume Rose 43d1f2919f Merge pull request #159 from guillaumerose/release
Cleanup release process. Create tarballs in Jenkins.
2019-07-17 16:11:26 +02:00
Guillaume Rose 7b072d4d2e Merge pull request #160 from ghatwala/master
Attempt to build this package on power
2019-07-17 16:11:16 +02:00
ghatwala c0c41f47b2 Attempt to build this package on power
Adding power support in travis.yml
- Adding ppc64le build and running "before_script_linux.sh" on intel only via travis.yml

Signed-off-by: ghatwala <ghatwala@us.ibm.com>
2019-07-17 14:58:17 +05:30
Guillaume Rose 69fb197018 Don’t use binaries built by Travis. Use Jenkins binaries.
Signed-off-by: Guillaume Rose <guillaume.rose@docker.com>
2019-07-17 10:35:02 +02:00
Guillaume Rose 86b653a9f1 Create tarballs in Jenkins.
Signed-off-by: Guillaume Rose <guillaume.rose@docker.com>
2019-07-17 10:34:50 +02:00
Guillaume Rose 88f932172b Remove old appveyor configuration
Signed-off-by: Guillaume Rose <guillaume.rose@docker.com>
2019-07-17 10:12:27 +02:00
Guillaume Rose 54f0238b6b Merge pull request #157 from guillaumerose/bump
Bump to 0.6.3
2019-07-16 13:54:57 +02:00
Guillaume Rose 2bf42cfd47 Bump to 0.6.3
Signed-off-by: Guillaume Rose <guillaume.rose@docker.com>
2019-07-16 10:46:07 +02:00
Guillaume Rose 1c9f7ede70 Merge pull request from GHSA-g9w7-h2f5-5vp2
Fix a double free in the List functions
2019-07-16 09:57:38 +02:00
Justin Cormack 87c80bfba5 Fix a double free in the List functions
The code was set up so that it would free the individual items and the data
in `freeListData`, but there was already a Go `defer` to free the data item,
resulting in a double free.

Remove the `free` in `freeListData` and leave the original one.

In addition, move the `defer` for freeing the list data before the error
check, so that the data is also free in the error case. This just removes
a minor leak.

This vulnerability was discovered by:
Jasiel Spelman of Trend Micro Zero Day Initiative and Trend Micro Team Nebula

Signed-off-by: Justin Cormack <justin.cormack@docker.com>
2019-07-01 14:41:30 +01:00
Silvin Lubecki 680ca48e6d Merge pull request #153 from thaJeztah/merge_0.6.2
Merge 'v0.6.2' to master
2019-06-20 14:53:21 +02:00
Sebastiaan van Stijn f6d4261609 Merge 'v0.6.2' to master
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-06-20 11:51:07 +02:00
Guillaume Rose f755519945 Merge pull request #148 from guillaumerose/jenkinsfile
Add Jenkinsfile
2019-05-07 08:12:18 -07:00
Guillaume Rose 11d9f9dba7 Add Jenkinsfile
Signed-off-by: Guillaume Rose <guillaume.rose@docker.com>
2019-05-07 16:04:36 +02:00
Ulrich VACHON beda055c57 Merge pull request #145 from ulrich/bump-to-v0.6.2
Bump version to 0.6.2
2019-05-02 12:50:58 +02:00
Ulrich VACHON 22b8706efa Bump version to 0.6.2
Signed-off-by: Ulrich VACHON <ulrich.vachon@docker.com>
2019-05-02 12:41:03 +02:00
96 changed files with 26350 additions and 1251 deletions
+1
View File
@@ -0,0 +1 @@
/bin
+4
View File
@@ -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)
+289
View File
@@ -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&mdash;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.
+12
View File
@@ -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&mdash;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.
+194
View File
@@ -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
+71
View File
@@ -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
View File
@@ -1,2 +1 @@
bin
release
/bin
+31
View File
@@ -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"
-50
View File
@@ -1,50 +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
before_deploy:
- sh ci/before_deploy.sh
deploy:
provider: releases
api_key:
secure: "$GITHUB_TOKEN"
# upload file artifacts using a glob expression.
# It requires both options `file_glob` and `file`:
# https://github.com/travis-ci/dpl/blob/master/lib/dpl/provider/releases.rb#L47-L53
file_glob: true
file: docker-credential-*-${TRAVIS_TAG}-amd64.tar.gz
# don't delete the artifacts from previous phases
skip_cleanup: true
# deploy when a new tag is pushed
on:
tags: true
branches:
only:
# Pushes and PR to the master branch
- master
# IMPORTANT Ruby regex to match tags. Required, or travis won't trigger deploys when a new tag
# is pushed. This regex matches semantic versions like v1.2.3-rc4+2016.02.22
- /^v\d+\.\d+\.\d+.*$/
-61
View File
@@ -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
View File
@@ -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
+59 -55
View File
@@ -1,82 +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
osxrelease: clean vet_osx lint fmt test osxcodesign
mkdir -p release
@echo "\nPackaging version ${VERSION}\n"
cd bin && tar cvfz ../release/docker-credential-osxkeychain-v$(VERSION)-amd64.tar.gz docker-credential-osxkeychain
.PHONY: cross
cross: # cross build all supported credential helpers
$(BUILDX_CMD) bake binaries
secretservice:
mkdir -p bin
go build -o bin/docker-credential-secretservice secretservice/cmd/main_linux.go
pass:
mkdir -p bin
go build -o bin/docker-credential-pass pass/cmd/main_linux.go
wincred:
mkdir -p bin
go build -o bin/docker-credential-wincred.exe wincred/cmd/main_windows.go
winrelease: clean vet_win lint fmt test wincred
mkdir -p release
@echo "\nPackaging version ${VERSION}\n"
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 $($*)
+37 -9
View File
@@ -1,3 +1,9 @@
[![GitHub release](https://img.shields.io/github/release/docker/docker-credential-helpers.svg?style=flat-square)](https://github.com/docker/docker-credential-helpers/releases/latest)
[![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?style=flat-square&logo=go&logoColor=white)](https://pkg.go.dev/github.com/docker/docker-credential-helpers)
[![Build Status](https://img.shields.io/github/actions/workflow/status/docker/docker-credential-helpers/build.yml?label=build&logo=github&style=flat-square)](https://github.com/docker/docker-credential-helpers/actions?query=workflow%3Abuild)
[![Codecov](https://img.shields.io/codecov/c/github/docker/docker-credential-helpers?logo=codecov&style=flat-square)](https://codecov.io/gh/docker/docker-credential-helpers)
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/docker-credential-helpers?style=flat-square)](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
-24
View File
@@ -1,24 +0,0 @@
image: Visual Studio 2015
environment:
GOPATH: c:\gopath
stack: go 1.8.7
clone_folder: c:\gopath\src\github.com\docker\docker-credential-helpers
clone_depth: 10
before_build:
- set PATH=%PATH%;C:\MinGW\bin;
build_script:
- mingw32-make vet_win wincred
test_script:
- mingw32-make test
deploy: off
artifacts:
- path: bin/docker-credential-wincred.exe
configuration: Release
-15
View File
@@ -1,15 +0,0 @@
set -ex
mkdir bin
case "$TRAVIS_OS_NAME" in
"osx")
go build -o bin/docker-credential-osxkeychain osxkeychain/cmd/main_darwin.go
cd bin
tar czf ../docker-credential-osxkeychain-${TRAVIS_TAG}-amd64.tar.gz docker-credential-osxkeychain
;;
"linux")
go build -o bin/docker-credential-secretservice secretservice/cmd/main_linux.go
cd bin
tar czf ../docker-credential-secretservice-${TRAVIS_TAG}-amd64.tar.gz docker-credential-secretservice
;;
esac
+9 -16
View File
@@ -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
View File
@@ -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)
}
}
+4 -6
View File
@@ -1,7 +1,6 @@
package client
import (
"fmt"
"io"
"os"
"os/exec"
@@ -30,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
View File
@@ -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
}
+20 -20
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,16 @@
package credentials
// Version holds a string describing the current version
const Version = "0.6.2"
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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+74
View File
@@ -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")]
}
+7
View File
@@ -0,0 +1,7 @@
module github.com/docker/docker-credential-helpers
go 1.19
require github.com/danieljoos/wincred v1.2.1
require golang.org/x/sys v0.15.0 // indirect
+9
View File
@@ -0,0 +1,9 @@
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/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
View File
@@ -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
View File
@@ -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>
@@ -224,5 +224,4 @@ void freeListData(char *** data, unsigned int length) {
for(int i=0; i<length; i++) {
free((*data)[i]);
}
free(*data);
}
@@ -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"
@@ -21,12 +24,19 @@ import (
// when the credentials are not in the keychain.
const errCredentialsNotFound = "The specified item could not be found in the keychain."
// errCredentialsNotFound is the specific error message returned by OS X
// when environment does not allow showing dialog to unlock keychain.
const errInteractionNotAllowed = "User interaction is not allowed."
// ErrInteractionNotAllowed is returned if keychain password prompt can not be shown.
var ErrInteractionNotAllowed = errors.New(`keychain cannot be accessed because the current session does not allow user interaction. The keychain may be locked; unlock it by running "security -v unlock-keychain ~/Library/Keychains/login.keychain-db" and try again`)
// Osxkeychain handles secrets using the OS X Keychain as store.
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 {
@@ -58,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
@@ -85,13 +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()
}
case errInteractionNotAllowed:
return "", "", ErrInteractionNotAllowed
default:
return "", "", errors.New(goMsg)
}
}
user := C.GoStringN(username, C.int(usernameLen))
pass := C.GoStringN(secret, C.int(secretLen))
@@ -109,18 +127,19 @@ func (h Osxkeychain) List() (map[string]string, error) {
defer C.free(unsafe.Pointer(acctsC))
var listLenC C.uint
errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
goMsg := C.GoString(errMsg)
if goMsg == errCredentialsNotFound {
return make(map[string]string), nil
}
return nil, errors.New(goMsg)
}
defer C.freeListData(&pathsC, listLenC)
defer C.freeListData(&acctsC, listLenC)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
switch goMsg := C.GoString(errMsg); goMsg {
case errCredentialsNotFound:
return make(map[string]string), nil
case errInteractionNotAllowed:
return nil, ErrInteractionNotAllowed
default:
return nil, errors.New(goMsg)
}
}
var listLen int
listLen = int(listLenC)
@@ -148,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 {
@@ -158,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
-213
View File
@@ -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)
}
}
+265
View File
@@ -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)
}
}
+35 -26
View File
@@ -1,7 +1,7 @@
// A `pass` based credential helper. Passwords are stored as arguments to pass
// of the form: "$PASS_FOLDER/base64-url(serverURL)/username". We base64-url
// encode the serverURL, because under the hood pass uses files and folders, so
// /s will get translated into additional folders.
// Package pass implements a `pass` based credential helper. Passwords are stored
// as arguments to pass of the form: "$PASS_FOLDER/base64-url(serverURL)/username".
// We base64-url encode the serverURL, because under the hood pass uses files and
// folders, so /s will get translated into additional folders.
package pass
import (
@@ -9,29 +9,32 @@ import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"io/fs"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"sync"
"github.com/docker/docker-credential-helpers/credentials"
)
const PASS_FOLDER = "docker-credential-helpers"
// PASS_FOLDER contains the directory where credentials are stored
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
@@ -79,34 +82,34 @@ func (p Pass) runPassHelper(stdinContent string, args ...string) (string, error)
}
// Add adds new credentials to the keychain.
func (h Pass) Add(creds *credentials.Credentials) error {
func (p Pass) Add(creds *credentials.Credentials) error {
if creds == nil {
return errors.New("missing credentials")
}
encoded := base64.URLEncoding.EncodeToString([]byte(creds.ServerURL))
_, err := h.runPass(creds.Secret, "insert", "-f", "-m", path.Join(PASS_FOLDER, encoded, creds.Username))
_, err := p.runPass(creds.Secret, "insert", "-f", "-m", path.Join(PASS_FOLDER, encoded, creds.Username))
return err
}
// Delete removes credentials from the store.
func (h Pass) Delete(serverURL string) error {
func (p Pass) Delete(serverURL string) error {
if serverURL == "" {
return errors.New("missing server url")
}
encoded := base64.URLEncoding.EncodeToString([]byte(serverURL))
_, err := h.runPass("", "rm", "-rf", path.Join(PASS_FOLDER, encoded))
_, err := p.runPass("", "rm", "-rf", path.Join(PASS_FOLDER, encoded))
return err
}
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.
@@ -115,20 +118,26 @@ 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.
func (h Pass) Get(serverURL string) (string, string, error) {
func (p Pass) Get(serverURL string) (string, string, error) {
if serverURL == "" {
return "", "", errors.New("missing server url")
}
@@ -137,7 +146,7 @@ func (h 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
@@ -153,12 +162,12 @@ func (h Pass) Get(serverURL string) (string, string, error) {
}
actual := strings.TrimSuffix(usernames[0].Name(), ".gpg")
secret, err := h.runPass("", "show", path.Join(PASS_FOLDER, encoded, actual))
secret, err := p.runPass("", "show", path.Join(PASS_FOLDER, encoded, actual))
return actual, secret, err
}
// List returns the stored URLs and corresponding usernames for a given credentials label
func (h Pass) List() (map[string]string, error) {
func (p Pass) List() (map[string]string, error) {
servers, err := listPassDir()
if err != nil {
return nil, err
-75
View File
@@ -1,75 +0,0 @@
package pass
import (
"strings"
"testing"
"github.com/docker/docker-credential-helpers/credentials"
)
func TestPassHelper(t *testing.T) {
helper := Pass{}
creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1",
Username: "nothing",
Secret: "isthebestmeshuggahalbum",
}
helper.Add(creds)
creds.ServerURL = "https://foobar.docker.io:9999/v2"
helper.Add(creds)
credsList, err := helper.List()
if err != nil {
t.Fatal(err)
}
for server, username := range credsList {
if !(strings.Contains(server, "2376") ||
strings.Contains(server, "9999")) {
t.Fatalf("invalid url: %s", creds.ServerURL)
}
if username != "nothing" {
t.Fatalf("invalid username: %v", username)
}
u, s, err := helper.Get(server)
if err != nil {
t.Fatal(err)
}
if u != username {
t.Fatalf("invalid username %s", u)
}
if s != "isthebestmeshuggahalbum" {
t.Fatalf("invalid secret: %s", s)
}
err = helper.Delete(server)
if err != nil {
t.Fatal(err)
}
username, _, err = helper.Get(server)
if err != nil {
t.Fatal(err)
}
if username != "" {
t.Fatalf("%s shouldn't exist any more", username)
}
}
credsList, err = helper.List()
if err != nil {
t.Fatal(err)
}
if len(credsList) != 0 {
t.Fatal("didn't delete all creds?")
}
}
+124
View File
@@ -0,0 +1,124 @@
//go:build !windows
package pass
import (
"strings"
"testing"
"github.com/docker/docker-credential-helpers/credentials"
)
func TestPassHelper(t *testing.T) {
creds := &credentials.Credentials{
ServerURL: "https://foobar.example.com:2376/v1",
Username: "nothing",
Secret: "isthebestmeshuggahalbum",
}
helper := Pass{}
if err := helper.checkInitialized(); err != nil {
t.Error(err)
}
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.Error(err)
}
for server, username := range credsList {
if !(strings.HasSuffix(server, "2376/v1") || strings.HasSuffix(server, "2375/v1")) {
t.Errorf("invalid url: %s", server)
}
if !(username == "foo" || username == "bar") {
t.Errorf("invalid username: %v", username)
}
u, s, err := helper.Get(server)
if err != nil {
t.Error(err)
}
if u != username {
t.Errorf("invalid username %s", u)
}
if s != "isthebestmeshuggahalbum" {
t.Errorf("invalid secret: %s", s)
}
if err := helper.Delete(server); err != nil {
t.Error(err)
}
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.Error(err)
}
if len(credsList) != 0 {
t.Error("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
View File
@@ -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
View File
@@ -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)
}
})
}
}
-15
View File
@@ -1,15 +0,0 @@
//+build go1.8
package registryurl
import (
url "net/url"
)
func GetHostname(u *url.URL) string {
return u.Hostname()
}
func GetPort(u *url.URL) string {
return u.Port()
}
-41
View File
@@ -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)
{
@@ -158,5 +158,4 @@ void freeListData(char *** data, unsigned int length) {
for(i=0; i<length; i++) {
free((*data)[i]);
}
free(*data);
}
@@ -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"
@@ -92,12 +95,13 @@ func (h Secretservice) List() (map[string]string, error) {
defer C.free(unsafe.Pointer(acctsC))
var listLenC C.uint
err := C.list(credsLabelC, &pathsC, &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")
}
defer C.freeListData(&pathsC, listLenC)
defer C.freeListData(&acctsC, listLenC)
if err != nil {
defer C.g_error_free(err)
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",
}
@@ -19,16 +21,15 @@ func TestSecretServiceHelper(t *testing.T) {
helper := Secretservice{}
// Check how many docker credentials we have when starting the test
old_auths, err := helper.List()
oldAuths, err := helper.List()
if err != nil {
t.Fatal(err)
}
// If any docker credentials with the tests values we are providing, we
// remove them as they probably come from a previous failed test
for k, v := range old_auths {
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)
}
@@ -36,7 +37,7 @@ func TestSecretServiceHelper(t *testing.T) {
}
// Check again how many docker credentials we have when starting the test
old_auths, err = helper.List()
oldAuths, err = helper.List()
if err != nil {
t.Fatal(err)
}
@@ -61,11 +62,11 @@ func TestSecretServiceHelper(t *testing.T) {
}
// We should have one more credential than before adding
new_auths, err := helper.List()
if err != nil || (len(new_auths)-len(old_auths) != 1) {
newAuths, err := helper.List()
if err != nil || (len(newAuths)-len(oldAuths) != 1) {
t.Fatal(err)
}
old_auths = new_auths
oldAuths = newAuths
// Deleting the credentials associated to current server url should succeed
if err := helper.Delete(creds.ServerURL); err != nil {
@@ -73,8 +74,8 @@ func TestSecretServiceHelper(t *testing.T) {
}
// We should have one less credential than before deleting
new_auths, err = helper.List()
if err != nil || (len(old_auths)-len(new_auths) != 1) {
newAuths, err = helper.List()
if err != nil || (len(oldAuths)-len(newAuths) != 1) {
t.Fatal(err)
}
}
+1
View File
@@ -0,0 +1 @@
*.go text eol=lf
+25
View File
@@ -0,0 +1,25 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
coverage.txt
+145
View File
@@ -0,0 +1,145 @@
wincred
=======
Go wrapper around the Windows Credential Manager API functions.
[![GitHub release](https://img.shields.io/github/release/danieljoos/wincred.svg?style=flat-square)](https://github.com/danieljoos/wincred/releases/latest)
[![Test Status](https://img.shields.io/github/actions/workflow/status/danieljoos/wincred/test.yml?label=test&logo=github&style=flat-square)](https://github.com/danieljoos/wincred/actions?query=workflow%3Atest)
[![Go Report Card](https://goreportcard.com/badge/github.com/danieljoos/wincred)](https://goreportcard.com/report/github.com/danieljoos/wincred)
[![Codecov](https://img.shields.io/codecov/c/github/danieljoos/wincred?logo=codecov&style=flat-square)](https://codecov.io/gh/danieljoos/wincred)
[![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/danieljoos/wincred)
Installation
------------
```Go
go get github.com/danieljoos/wincred
```
Usage
-----
See the following examples:
### Create and store a new generic credential object
```Go
package main
import (
"fmt"
"github.com/danieljoos/wincred"
)
func main() {
cred := wincred.NewGenericCredential("myGoApplication")
cred.CredentialBlob = []byte("my secret")
err := cred.Write()
if err != nil {
fmt.Println(err)
}
}
```
### Retrieve a credential object
```Go
package main
import (
"fmt"
"github.com/danieljoos/wincred"
)
func main() {
cred, err := wincred.GetGenericCredential("myGoApplication")
if err == nil {
fmt.Println(string(cred.CredentialBlob))
}
}
```
### Remove a credential object
```Go
package main
import (
"fmt"
"github.com/danieljoos/wincred"
)
func main() {
cred, err := wincred.GetGenericCredential("myGoApplication")
if err != nil {
fmt.Println(err)
return
}
cred.Delete()
}
```
### List all available credentials
```Go
package main
import (
"fmt"
"github.com/danieljoos/wincred"
)
func main() {
creds, err := wincred.List()
if err != nil {
fmt.Println(err)
return
}
for i := range(creds) {
fmt.Println(creds[i].TargetName)
}
}
```
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.
+40 -45
View File
@@ -1,88 +1,83 @@
// +build windows
package wincred
import (
"encoding/binary"
"reflect"
"syscall"
"time"
"unicode/utf16"
"unsafe"
syscall "golang.org/x/sys/windows"
)
var nullPointer = unsafe.Pointer(uintptr(0))
// Create a Go string using a pointer to a zero-terminated UTF 16 encoded string.
// See github.com/AllenDang/w32
func utf16PtrToString(wstr *uint16) string {
if wstr != nil {
buf := make([]uint16, 0, 256)
for ptr := uintptr(unsafe.Pointer(wstr)); ; ptr += 2 {
rune := *(*uint16)(unsafe.Pointer(ptr))
if rune == 0 {
return string(utf16.Decode(buf))
}
buf = append(buf, rune)
}
}
return ""
}
// Create a byte array from a given UTF 16 char array
// utf16ToByte creates a byte array from a given UTF 16 char array.
func utf16ToByte(wstr []uint16) (result []byte) {
result = make([]byte, len(wstr)*2)
for i, _ := range wstr {
for i := range wstr {
binary.LittleEndian.PutUint16(result[(i*2):(i*2)+2], wstr[i])
}
return
}
// Copies the given C byte array to a Go byte array (see `C.GoBytes`)
func goBytes(src unsafe.Pointer, len uint32) []byte {
if src == nullPointer {
// utf16FromString creates a UTF16 char array from a string.
func utf16FromString(str string) []uint16 {
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`).
// This function avoids having cgo as dependency.
func goBytes(src uintptr, len uint32) []byte {
if src == uintptr(0) {
return []byte{}
}
slice := (*[1 << 30]byte)(src)[0:len]
rv := make([]byte, len)
copy(rv, slice)
return rv[:]
copy(rv, *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: src,
Len: int(len),
Cap: int(len),
})))
return rv
}
// Convert the given CREDENTIAL struct to a more usable structure
func nativeToCredential(cred *nativeCREDENTIAL) (result *Credential) {
if unsafe.Pointer(cred) == nullPointer {
func sysToCredential(cred *sysCREDENTIAL) (result *Credential) {
if cred == nil {
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(unsafe.Pointer(cred.CredentialBlob), cred.CredentialBlobSize)
result.CredentialBlob = goBytes(cred.CredentialBlob, cred.CredentialBlobSize)
result.Attributes = make([]CredentialAttribute, cred.AttributeCount)
attrSliceHeader := reflect.SliceHeader{
attrSlice := *(*[]sysCREDENTIAL_ATTRIBUTE)(unsafe.Pointer(&reflect.SliceHeader{
Data: cred.Attributes,
Len: int(cred.AttributeCount),
Cap: int(cred.AttributeCount),
}
attrSlice := *(*[]nativeCREDENTIAL_ATTRIBUTE)(unsafe.Pointer(&attrSliceHeader))
}))
for i, attr := range attrSlice {
resultAttr := &result.Attributes[i]
resultAttr.Keyword = utf16PtrToString(attr.Keyword)
resultAttr.Value = goBytes(unsafe.Pointer(attr.Value), attr.ValueSize)
resultAttr.Keyword = syscall.UTF16PtrToString(attr.Keyword)
resultAttr.Value = goBytes(attr.Value, attr.ValueSize)
}
return result
}
// Convert the given Credential object back to a CREDENTIAL struct, which can be used for calling the
// Windows APIs
func nativeFromCredential(cred *Credential) (result *nativeCREDENTIAL) {
func sysFromCredential(cred *Credential) (result *sysCREDENTIAL) {
if cred == nil {
return nil
}
result = new(nativeCREDENTIAL)
result = new(sysCREDENTIAL)
result.Flags = 0
result.Type = 0
result.TargetName, _ = syscall.UTF16PtrFromString(cred.TargetName)
@@ -96,13 +91,13 @@ func nativeFromCredential(cred *Credential) (result *nativeCREDENTIAL) {
}
result.Persist = uint32(cred.Persist)
result.AttributeCount = uint32(len(cred.Attributes))
attributes := make([]nativeCREDENTIAL_ATTRIBUTE, len(cred.Attributes))
attributes := make([]sysCREDENTIAL_ATTRIBUTE, len(cred.Attributes))
if len(attributes) > 0 {
result.Attributes = uintptr(unsafe.Pointer(&attributes[0]))
} else {
result.Attributes = 0
}
for i, _ := range cred.Attributes {
for i := range cred.Attributes {
inAttr := &cred.Attributes[i]
outAttr := &attributes[i]
outAttr.Keyword, _ = syscall.UTF16PtrFromString(inAttr.Keyword)
+11
View File
@@ -0,0 +1,11 @@
// +build !windows
package wincred
func utf16ToByte(...interface{}) []byte {
return nil
}
func utf16FromString(...interface{}) []uint16 {
return nil
}
-137
View File
@@ -1,137 +0,0 @@
package wincred
import (
"syscall"
"unsafe"
)
var (
modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
procCredRead proc = modadvapi32.NewProc("CredReadW")
procCredWrite proc = modadvapi32.NewProc("CredWriteW")
procCredDelete proc = modadvapi32.NewProc("CredDeleteW")
procCredFree proc = modadvapi32.NewProc("CredFree")
procCredEnumerate proc = modadvapi32.NewProc("CredEnumerateW")
)
// Interface for syscall.Proc: helps testing
type proc interface {
Call(a ...uintptr) (r1, r2 uintptr, lastErr error)
}
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374788(v=vs.85).aspx
type nativeCREDENTIAL struct {
Flags uint32
Type uint32
TargetName *uint16
Comment *uint16
LastWritten syscall.Filetime
CredentialBlobSize uint32
CredentialBlob uintptr
Persist uint32
AttributeCount uint32
Attributes uintptr
TargetAlias *uint16
UserName *uint16
}
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374790(v=vs.85).aspx
type nativeCREDENTIAL_ATTRIBUTE struct {
Keyword *uint16
Flags uint32
ValueSize uint32
Value uintptr
}
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374788(v=vs.85).aspx
type nativeCRED_TYPE uint32
const (
naCRED_TYPE_GENERIC nativeCRED_TYPE = 0x1
naCRED_TYPE_DOMAIN_PASSWORD nativeCRED_TYPE = 0x2
naCRED_TYPE_DOMAIN_CERTIFICATE nativeCRED_TYPE = 0x3
naCRED_TYPE_DOMAIN_VISIBLE_PASSWORD nativeCRED_TYPE = 0x4
naCRED_TYPE_GENERIC_CERTIFICATE nativeCRED_TYPE = 0x5
naCRED_TYPE_DOMAIN_EXTENDED nativeCRED_TYPE = 0x6
naERROR_NOT_FOUND = "Element not found."
)
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374804(v=vs.85).aspx
func nativeCredRead(targetName string, typ nativeCRED_TYPE) (*Credential, error) {
var pcred uintptr
targetNamePtr, _ := syscall.UTF16PtrFromString(targetName)
ret, _, err := procCredRead.Call(
uintptr(unsafe.Pointer(targetNamePtr)),
uintptr(typ),
0,
uintptr(unsafe.Pointer(&pcred)),
)
if ret == 0 {
return nil, err
}
defer procCredFree.Call(pcred)
return nativeToCredential((*nativeCREDENTIAL)(unsafe.Pointer(pcred))), nil
}
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa375187(v=vs.85).aspx
func nativeCredWrite(cred *Credential, typ nativeCRED_TYPE) error {
ncred := nativeFromCredential(cred)
ncred.Type = uint32(typ)
ret, _, err := procCredWrite.Call(
uintptr(unsafe.Pointer(ncred)),
0,
)
if ret == 0 {
return err
}
return nil
}
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa374787(v=vs.85).aspx
func nativeCredDelete(cred *Credential, typ nativeCRED_TYPE) error {
targetNamePtr, _ := syscall.UTF16PtrFromString(cred.TargetName)
ret, _, err := procCredDelete.Call(
uintptr(unsafe.Pointer(targetNamePtr)),
uintptr(typ),
0,
)
if ret == 0 {
return err
}
return nil
}
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa374794(v=vs.85).aspx
func nativeCredEnumerate(filter string, all bool) ([]*Credential, error) {
var count int
var pcreds uintptr
var filterPtr uintptr
if !all {
filterUtf16Ptr, _ := syscall.UTF16PtrFromString(filter)
filterPtr = uintptr(unsafe.Pointer(filterUtf16Ptr))
} else {
filterPtr = 0
}
ret, _, err := procCredEnumerate.Call(
filterPtr,
0,
uintptr(unsafe.Pointer(&count)),
uintptr(unsafe.Pointer(&pcreds)),
)
if ret == 0 {
return nil, err
}
defer procCredFree.Call(pcreds)
pcredsSlice := (*[1 << 30]uintptr)(unsafe.Pointer(pcreds))[:count:count]
creds := make([]*Credential, count)
for i := range creds {
creds[i] = nativeToCredential((*nativeCREDENTIAL)(unsafe.Pointer(pcredsSlice[i])))
}
return creds, nil
}
+148
View File
@@ -0,0 +1,148 @@
//go:build windows
// +build windows
package wincred
import (
"reflect"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var (
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 = modadvapi32.NewProc("CredEnumerateW")
)
// Interface for syscall.Proc: helps testing
type proc interface {
Call(a ...uintptr) (r1, r2 uintptr, lastErr error)
}
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw
type sysCREDENTIAL struct {
Flags uint32
Type uint32
TargetName *uint16
Comment *uint16
LastWritten windows.Filetime
CredentialBlobSize uint32
CredentialBlob uintptr
Persist uint32
AttributeCount uint32
Attributes uintptr
TargetAlias *uint16
UserName *uint16
}
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credential_attributew
type sysCREDENTIAL_ATTRIBUTE struct {
Keyword *uint16
Flags uint32
ValueSize uint32
Value uintptr
}
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw
type sysCRED_TYPE uint32
const (
sysCRED_TYPE_GENERIC sysCRED_TYPE = 0x1
sysCRED_TYPE_DOMAIN_PASSWORD sysCRED_TYPE = 0x2
sysCRED_TYPE_DOMAIN_CERTIFICATE sysCRED_TYPE = 0x3
sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD sysCRED_TYPE = 0x4
sysCRED_TYPE_GENERIC_CERTIFICATE sysCRED_TYPE = 0x5
sysCRED_TYPE_DOMAIN_EXTENDED sysCRED_TYPE = 0x6
// https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes
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, _ := windows.UTF16PtrFromString(targetName)
ret, _, err := syscall.SyscallN(
procCredRead.Addr(),
uintptr(unsafe.Pointer(targetNamePtr)),
uintptr(typ),
0,
uintptr(unsafe.Pointer(&pcred)),
)
if ret == 0 {
return nil, err
}
defer procCredFree.Call(uintptr(unsafe.Pointer(pcred)))
return sysToCredential(pcred), nil
}
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credwritew
func sysCredWrite(cred *Credential, typ sysCRED_TYPE) error {
ncred := sysFromCredential(cred)
ncred.Type = uint32(typ)
ret, _, err := procCredWrite.Call(
uintptr(unsafe.Pointer(ncred)),
0,
)
if ret == 0 {
return err
}
return nil
}
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creddeletew
func sysCredDelete(cred *Credential, typ sysCRED_TYPE) error {
targetNamePtr, _ := windows.UTF16PtrFromString(cred.TargetName)
ret, _, err := procCredDelete.Call(
uintptr(unsafe.Pointer(targetNamePtr)),
uintptr(typ),
0,
)
if ret == 0 {
return err
}
return nil
}
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credenumeratew
func sysCredEnumerate(filter string, all bool) ([]*Credential, error) {
var count int
var pcreds uintptr
var filterPtr *uint16
if !all {
filterPtr, _ = windows.UTF16PtrFromString(filter)
}
ret, _, err := syscall.SyscallN(
procCredEnumerate.Addr(),
uintptr(unsafe.Pointer(filterPtr)),
0,
uintptr(unsafe.Pointer(&count)),
uintptr(unsafe.Pointer(&pcreds)),
)
if ret == 0 {
return nil, err
}
defer procCredFree.Call(pcreds)
credsSlice := *(*[]*sysCREDENTIAL)(unsafe.Pointer(&reflect.SliceHeader{
Data: pcreds,
Len: count,
Cap: count,
}))
creds := make([]*Credential, count, count)
for i, cred := range credsSlice {
creds[i] = sysToCredential(cred)
}
return creds, nil
}
+36
View File
@@ -0,0 +1,36 @@
// +build !windows
package wincred
import (
"errors"
"syscall"
)
const (
sysCRED_TYPE_GENERIC = 0
sysCRED_TYPE_DOMAIN_PASSWORD = 0
sysCRED_TYPE_DOMAIN_CERTIFICATE = 0
sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 0
sysCRED_TYPE_GENERIC_CERTIFICATE = 0
sysCRED_TYPE_DOMAIN_EXTENDED = 0
sysERROR_NOT_FOUND = syscall.Errno(1)
sysERROR_INVALID_PARAMETER = syscall.Errno(1)
)
func sysCredRead(...interface{}) (*Credential, error) {
return nil, errors.New("Operation not supported")
}
func sysCredWrite(...interface{}) error {
return errors.New("Operation not supported")
}
func sysCredDelete(...interface{}) error {
return errors.New("Operation not supported")
}
func sysCredEnumerate(...interface{}) ([]*Credential, error) {
return nil, errors.New("Operation not supported")
}
+32
View File
@@ -4,19 +4,38 @@ import (
"time"
)
// CredentialPersistence describes one of three persistence modes of a credential.
// A detailed description of the available modes can be found on
// Docs: https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw
type CredentialPersistence uint32
const (
// PersistSession indicates that the credential only persists for the life
// of the current Windows login session. Such a credential is not visible in
// any other logon session, even from the same user.
PersistSession CredentialPersistence = 0x1
// PersistLocalMachine indicates that the credential persists for this and
// all subsequent logon sessions on this local machine/computer. It is
// however not visible for logon sessions of this user on a different
// machine.
PersistLocalMachine CredentialPersistence = 0x2
// PersistEnterprise indicates that the credential persists for this and all
// subsequent logon sessions for this user. It is also visible for logon
// sessions on different computers.
PersistEnterprise CredentialPersistence = 0x3
)
// CredentialAttribute represents an application-specific attribute of a credential.
type CredentialAttribute struct {
Keyword string
Value []byte
}
// Credential is the basic credential structure.
// A credential is identified by its target name.
// The actual credential secret is available in the CredentialBlob field.
type Credential struct {
TargetName string
Comment string
@@ -28,10 +47,23 @@ type Credential struct {
Persist CredentialPersistence
}
// GenericCredential holds a credential for generic usage.
// It is typically defined and used by applications that need to manage user
// secrets.
//
// More information about the available kinds of credentials of the Windows
// Credential Management API can be found on Docs:
// https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/kinds-of-credentials
type GenericCredential struct {
Credential
}
// DomainPassword holds a domain credential that is typically used by the
// operating system for user logon.
//
// More information about the available kinds of credentials of the Windows
// Credential Management API can be found on Docs:
// https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/kinds-of-credentials
type DomainPassword struct {
Credential
}
+58 -24
View File
@@ -1,19 +1,39 @@
// Package wincred provides primitives for accessing the Windows Credentials Management API.
// This includes functions for retrieval, listing and storage of credentials as well as Go structures for convenient access to the credential data.
//
// A more detailed description of Windows Credentials Management can be found on
// Docs: https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/credentials-management
package wincred
import (
"syscall"
import "errors"
const (
// ErrElementNotFound is the error that is returned if a requested element cannot be found.
// This error constant can be used to check if a credential could not be found.
ErrElementNotFound = sysERROR_NOT_FOUND
// ErrInvalidParameter is the error that is returned for invalid parameters.
// 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
)
// Get the generic credential with the given name from Windows credential manager
// GetGenericCredential fetches the generic credential with the given name from Windows credential manager.
// It returns nil and an error if the credential could not be found or an error occurred.
func GetGenericCredential(targetName string) (*GenericCredential, error) {
cred, err := nativeCredRead(targetName, naCRED_TYPE_GENERIC)
cred, err := sysCredRead(targetName, sysCRED_TYPE_GENERIC)
if cred != nil {
return &GenericCredential{*cred}, err
return &GenericCredential{Credential: *cred}, err
}
return nil, err
}
// Create a new generic credential with the given name
// NewGenericCredential creates a new generic credential object with the given name.
// The persist mode of the newly created object is set to a default value that indicates local-machine-wide storage.
// The credential object is NOT yet persisted to the Windows credential vault.
func NewGenericCredential(targetName string) (result *GenericCredential) {
result = new(GenericCredential)
result.TargetName = targetName
@@ -21,28 +41,31 @@ func NewGenericCredential(targetName string) (result *GenericCredential) {
return
}
// Persist the credential to Windows credential manager
// Write persists the generic credential object to Windows credential manager.
func (t *GenericCredential) Write() (err error) {
err = nativeCredWrite(&t.Credential, naCRED_TYPE_GENERIC)
err = sysCredWrite(&t.Credential, sysCRED_TYPE_GENERIC)
return
}
// Delete the credential from Windows credential manager
// Delete removes the credential object from Windows credential manager.
func (t *GenericCredential) Delete() (err error) {
err = nativeCredDelete(&t.Credential, naCRED_TYPE_GENERIC)
err = sysCredDelete(&t.Credential, sysCRED_TYPE_GENERIC)
return
}
// Get the domain password credential with the given target host name
// GetDomainPassword fetches the domain-password credential with the given target host name from Windows credential manager.
// It returns nil and an error if the credential could not be found or an error occurred.
func GetDomainPassword(targetName string) (*DomainPassword, error) {
cred, err := nativeCredRead(targetName, naCRED_TYPE_DOMAIN_PASSWORD)
cred, err := sysCredRead(targetName, sysCRED_TYPE_DOMAIN_PASSWORD)
if cred != nil {
return &DomainPassword{*cred}, err
return &DomainPassword{Credential: *cred}, err
}
return nil, err
}
// Create a new domain password credential used for login to the given target host name
// NewDomainPassword creates a new domain-password credential used for login to the given target host name.
// The persist mode of the newly created object is set to a default value that indicates local-machine-wide storage.
// The credential object is NOT yet persisted to the Windows credential vault.
func NewDomainPassword(targetName string) (result *DomainPassword) {
result = new(DomainPassword)
result.TargetName = targetName
@@ -50,28 +73,39 @@ func NewDomainPassword(targetName string) (result *DomainPassword) {
return
}
// Persist the domain password credential to Windows credential manager
// Write persists the domain-password credential to Windows credential manager.
func (t *DomainPassword) Write() (err error) {
err = nativeCredWrite(&t.Credential, naCRED_TYPE_DOMAIN_PASSWORD)
err = sysCredWrite(&t.Credential, sysCRED_TYPE_DOMAIN_PASSWORD)
return
}
// Delete the domain password credential from Windows credential manager
// Delete removes the domain-password credential from Windows credential manager.
func (t *DomainPassword) Delete() (err error) {
err = nativeCredDelete(&t.Credential, naCRED_TYPE_DOMAIN_PASSWORD)
err = sysCredDelete(&t.Credential, sysCRED_TYPE_DOMAIN_PASSWORD)
return
}
// Set the CredentialBlob field of a domain password credential
// using an UTF16 encoded password string
// SetPassword sets the CredentialBlob field of a domain password credential to the given string.
func (t *DomainPassword) SetPassword(pw string) {
t.CredentialBlob = utf16ToByte(syscall.StringToUTF16(pw))
t.CredentialBlob = utf16ToByte(utf16FromString(pw))
}
// List the contents of the Credentials store
// List retrieves all credentials of the Credentials store.
func List() ([]*Credential, error) {
creds, err := nativeCredEnumerate("", true)
if err != nil && err.Error() == naERROR_NOT_FOUND {
creds, err := sysCredEnumerate("", true)
if err != nil && errors.Is(err, ErrElementNotFound) {
// Ignore ERROR_NOT_FOUND and return an empty list instead
creds = []*Credential{}
err = nil
}
return creds, err
}
// FilteredList retrieves the list of credentials from the Credentials store that match the given filter.
// The filter string defines the prefix followed by an asterisk for the `TargetName` attribute of the credentials.
func FilteredList(filter string) ([]*Credential, error) {
creds, err := sysCredEnumerate(filter, false)
if err != nil && errors.Is(err, ErrElementNotFound) {
// Ignore ERROR_NOT_FOUND and return an empty list instead
creds = []*Credential{}
err = nil
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+22
View File
@@ -0,0 +1,22 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.
+12
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
}
File diff suppressed because it is too large Load Diff
+257
View File
@@ -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
File diff suppressed because it is too large Load Diff
+22
View File
@@ -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
View File
@@ -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
}
File diff suppressed because it is too large Load Diff
+3382
View File
File diff suppressed because it is too large Load Diff
+35
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
File diff suppressed because it is too large Load Diff
+149
View File
@@ -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}}
)
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
# github.com/danieljoos/wincred v1.2.1
## explicit; go 1.18
github.com/danieljoos/wincred
# 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 (
+291
View File
@@ -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)
}
}
-244
View File
@@ -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)
}
}