GithubHelp home page GithubHelp logo

suse / zypper-docker Goto Github PK

View Code? Open in Web Editor NEW
44.0 30.0 17.0 46.86 MB

Simple patch and update solution for Docker images

License: Apache License 2.0

Go 85.58% Makefile 1.03% Shell 10.24% Perl 2.84% Dockerfile 0.32%
zypper docker security go

zypper-docker's Introduction

zypper-docker Build Status GoDoc

The zypper-docker command line tool provides a quick way to patch and update Docker Images based on either SUSE Linux Enterprise or openSUSE.

zypper-docker mimics zypper command line syntax to ease its usage. This application relies on zypper to perform the actual operations against Docker images.

asciicast

Targetting Docker daemons running on remote machines

zypper-docker can interact with docker daemons running on remote machines. To do that it uses the same environment variables of the docker client.

docker-machine can be used to configure the remote Docker host and setup the local environment variables.

asciicast

Commands

Listing images

The images command is similar to the one from Docker, but this one only lists images that are based on either openSUSE or SUSE Linux Enterprise. Here's an example:

mssola:~ $ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
opensuse            latest              c7ff47bc7ebb        13 days ago          254.5 MB
busybox             latest              8c2e06607696        3 months ago         2.43 MB
mssola:~ $ zypper-docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
opensuse            latest              c7ff47bc7ebb        13 days ago          254.5 MB

Updates

First of all, you can check whether an image has pending updates or not by using the list-updates command. The usage is as follows:

$ zypper docker list-updates (lu) <image>

Similarly, there is the list-updates-container that does the same but targeting an already running container. Note that this command does not touch the running container, but it just detects the image in which the running container is based on, and then it just performs list-updates for the according image. There's a short video about list-updates in action here:

asciicast

But more important than listing updates is to actually install them. You can do this with the update command. It has the following usage:

$ zypper docker update (up) [options] <image> <new-image>

If there are updates, this command will create a new Docker image based on the given image, but with the needed updates already installed. Therefore, note that zypper-docker will never change anything from the old image. More than that, this command will refuse to overwrite an already existing Docker image.

The available options are:

  • --skip-interactive: This will skip interactive patches, that is, those that need reboot, contain a message, or update a package whose license needs to be confirmed.
  • --with-interactive: Avoid skipping of interactive patches when in non-interactive mode.
  • -l, --auto-agree-with-licenses: Automatically say yes to third party license confirmation prompt. By using this option, you choose to agree with licenses of all third-party software this command will install. This option is particularly useful for administrators installing the same set of packages on multiple machines (by an automated process) and have the licenses confirmed before.
  • --no-recommends: By default, zypper installs also packages recommended by the requested ones. This option causes the recommended packages to be ignored and only the required ones to be installed.
  • --replacefiles: Install the packages even if they replace files from other, already installed, packages. Default is to treat file conflicts as an error.
  • --author: commit author to associate with the new layer. By default it uses the canonical name of the current user.
  • --message: commit message to be associated with the new layer. If no message was provided, zypper-docker will write: "[zypper-docker] update".

You can find a small video about the update Command here:

asciicast

Patches

The operations that can be done for patches is very similar to the ones that can be done for updates. Therefore, the list-patches and the list-patches-container commands are almost identical to the ones for updates. In particular, these are their usage:

$ zypper docker list-patches (lp) [options] image

$ zypper docker list-patches-container (lpc) [options] image

As you will notice, both of these commands accept some options. For these commands the user can filter the results according to the following attributes:

The available options are:

  • --bugzilla[=#]: List available needed patches for all Bugzilla issues, or issues whose number matches the given string.
  • --cve[=#]: List available needed patches for all CVE issues, or issues whose number matches the given string.
  • --date YYYY-MM-DD: List patches issued up to, but not including, the specified date.
  • --issues[=string]: Look for issues whose number, summary, or description matches the specified string. Issues found by number are displayed separately from those found by descriptions. In the latter case, use zypper patch-info patchname to get information about issues the patch fixes.
  • -g, --category category: List available patches in the specified category.

You can find a small video on listing patches here:

asciicast

Interestingly enough, zypper can also just check whether there are patches available at all. This is really convenient for using zypper inside of scripts. zypper-docker also implements this in the form of the patch-check command. This is its usage:

$ zypper docker patch-check (pchk) image

This command will exit with a status code of 100 if there are patches available, and 101 if there are not.

Besides listing and checking for patches, you can also of course install them. You do that with the patch command. It has the following usage:

$ zypper docker patch [options] image new-image

Similarly to the update command, this command will not change the original change, but it creates a new patched image. This command also takes into account that the new image does not overwrite an already existing one. The arguments that can be passed to this command are as follows:

  • --bugzilla #: Install patch fixing a Bugzilla issue specified by number. Use list-patches --bugzilla command to get a list of available needed patches for specific issues.
  • --cve #: Install patch fixing a MITRE’s CVE issue specified by number. Use list-patches --cve command to get a list of available needed patches for specific issues.
  • --date YYYY-MM-DD: Install patches issued up to, but not including, the specified date.
  • -g, --category category: Install all patches in the specified category. Use list-patches --category command to get a list of available patches for a specific category.
  • --skip-interactive: Skip interactive patches.
  • --with-interactive: Avoid skipping of interactive patches when in non-interactive mode.
  • -l, --auto-agree-with-licenses: See the update command for description of this option.
  • --no-recommends: By default, zypper installs also packages recommended by the requested ones. This option causes the recommended packages to be ignored and only the required ones to be installed.
  • --replacefiles: Install the packages even if they replace files from other, already installed, packages. Default is to treat file conflicts as an error.
  • --author: commit author to associate with the new layer. By default it uses the canonical name of the current user.
  • --message: commit message to be associated with the new layer. If no message was provided, zypper-docker will write: "[zypper-docker] patch".

You can find a small video showing off the patch command here:

asciicast

List all the missing updates

Lastly, zypper-docker also has the ps command. This command traverses through all the running containers and investigates which of them are based on images that have been recently upgraded. Therefore, this command does not provide feedback about all the possible SUSE containers, only the ones that have been updated/patched with the update and patch commands.

This command doesn't have any options, so the usage is quite straight-forward:

$ zypper docker ps

Local cache

Note that some of these commands might be expensive. That's why some of the needed data is cached into a single file. This file is named docker-zypper.json. This cache file normally resides inside of the $HOME/.cache directory. However, if there is some problem with this directory, it might get saved inside of the /tmp directory.

Development environment

It is possible to run all the test suite and the code analysis tool using docker.

Build the docker images

The tests and code analysis tool are going to be executed ran using both the latest stable version of Go and the current experimental version.

To build these docker images type:

$ make build

Run the tests

To run the test suite and the code analysis tools against all Go versions, type:

$ make test

Integration tests

The integration tests invoke the zypper-docker binary and test different scenarios. They are written using RSpec and are located under /spec. The integration tests can be started by doing:

make test_integration

This will build a Docker image containing all the software (RSpec, plus other Ruby gems) required to run the tests. The image will be started, the socket used by the Docker daemon on the host will be mounted inside of the new container. That makes possible to invoke the docker client from within the container itself.

License

Licensed under the Apache License, Version 2.0. See LICENSE for the full license text.

zypper-docker's People

Contributors

chawlanikhil24 avatar cyphar avatar eliroca avatar flavio avatar golint-fixer avatar jordimassaguerpla avatar mssola avatar parlt91 avatar vrothberg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zypper-docker's Issues

Provide templating and filtering

To make the life easier while detecting vulns and to properly parse them we should provide more options for some kind of templating the output and also for filtering the output properly.

Than we can make scripts like this much cleaner:

zypper-docker lp $IMAGE > $WORK_DIR/output
FAILED=false
for SEV in $SEVERITY; do
  echo "Looking for $SEV issues..."
  RES=false
  (grep "| $SEV " $WORK_DIR/output | grep "security") || RES=true
  if [ "$RES" == "false" ]; then
    FAILED=true
    echo "************ $SEV issues found! *************"
  fi
done

zypper-docker doesn't show all images

On fresh SLE12 installation with the new zypper-docker package (1.1.0-4.1):

server:~ # docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
sles12              latest              bb7d9e625838        5 weeks ago         258.2 MB
opensuse            latest              34598a18d02c        5 weeks ago         93.99 MB

but with zypper-docker command:

server:~ # zypper-docker images                                                                                                                                                                   
Inspecting image 2/2
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
opensuse            latest              34598a18d02c        5 weeks ago         93.99 MB

and in a log file I can see the errors:

server:~ # tail .zypper-docker.log                                                                                                                                                                
[ps] 2015/11/24 13:07:20 Decoding of cache file failed: EOF
[ps] 2015/11/24 13:07:24 Could not execute command 'zypper' successfully in image 'bb7d9e62583876b0828c025aa4cfb284a8f6afa6326a0cdafd145445f1f3cb03': Timed out when waiting for a container.

If I run zypper-docker -f images then I can see all images:

server:~ # zypper-docker -f images                                                                                                                                                                
Inspecting image 2/2
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
sles12              latest              bb7d9e625838        5 weeks ago         258.2 MB
opensuse            latest              34598a18d02c        5 weeks ago         93.99 MB

Update Dockerfiles and tests

Move away from 13.2 and update to use more recent version of openSUSE.

  • update travis file
  • update Dockerfiles
  • update tests

Problem retrieving the repository index file for service 'container-suseconnect'

Don´t know, if it is a problem of zypper-docker or sle2docker or the sle2docker-image....

zypper-docker lp suse/sles12

Refreshing service 'container-suseconnect'.
Problem retrieving the repository index file for service 'container-suseconnect':
[container-suseconnect|file:/usr/lib/zypp/plugins/services/container-suseconnect]
Warning: Skipping service 'container-suseconnect' because of the above error.
Warning: There are no enabled repositories defined.
Use 'zypper addrepo' or 'zypper modifyrepo' commands to add or enable repositories.

zypper-docker-1.1.1-8.1.x86_64

sle2docker-0.4.2-14.3.x86_64

Image:

  • sles12-docker.x86_64-1.1.0-Build10.1

Seems like the SCCcredentials is not injected into the image. When I create a new one with the following Dockerfile-Command:

ADD SCCcredentials /etc/zypp/credentials.d/

It works....

zypper-docker help: mention the short form of commands

The zypper-docker man-page mentions both the long and short form of the commands

list-patches, lp
list-patches-container, lpc
list-updates, lu
list-updates-container, luc
patch-check, pchk
patch-check-container, pchkc

However zypper-docker [command] --help only lists the long form.

Since the code does seem to have the short-form commands implemented, we should update the online help to be in line with the documentation.

Fixes: bsc#1022052

Resize the TTY

By default, container logs are sized like this: height=40 and width=80. This can be inconvenient for logs that are just too wide. For example, a simple zypper lp already breaks this size. In this case, docker will cut the output to 80 columns and append an arrow like this:

Repository | Name              | Category    | Severity | Status | Summary      
-----------+-------------------+-------------+----------+--------+--------------
OSS Update | openSUSE-2015-345 | recommended | moderate | needed | Recommended->
OSS Update | openSUSE-2015-497 | security    | moderate | needed | Security up->

We need to resize the TTY by performing a call the resize endpoint. Sadly, the dockerclient does not implement a function targeting this endpoint.

Therefore, the solution would be to update our current fork of the dockerclient library and submit a PR to upstream. Meanwhile we would still keep on using our fork.

output image ignores namespace

run
zypper docker patch suse/sles11sp4:latest suse/sles11sp4:patched

and the output image will be

sles11sp4:patched

while I should have expected to be

suse/sles11sp4:patched

Move to the new `docker/engine-api`

Docker recently released the engine client API in a separate project. This effectively replaces the need for the docker client being used. One important thing to note is that Docker is using this library in current master. This fixes notable issues with the docker client we're using: missing types/endpoints, outdated API, missing parameters, etc.

--help pages aren't helpful for subcommand arguments

% ./zypper-docker --help                                                   
NAME:
   zypper-docker - Patching Docker images safely

USAGE:
   zypper-docker [global options] command [command options] [arguments...]

VERSION:
   1.1.2

COMMANDS:
   images                       List all the images based on either OpenSUSE or SLES
   list-updates, lu             List all the available updates
   list-updates-container, luc  List all the available updates for the given container
   update, up                   Install the available updates
   list-patches, lp             List all the available patches
   list-patches-container, lpc  List all the available patches for the given container
   patch                        Install the available patches
   patch-check, pchk            Check for patches
   patch-check-container, pchkc Check for patches available for the given container
   ps                           List all the containers that are outdated
   help, h                      Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --no-gpg-checks                                      Ignore GPG check failures and continue
   --gpg-auto-import-keys                               If new repository signing key is found, do not ask what to do; trust and import it automatically
   -f, --force                                          Ignore all the local caches
   -d, --debug                                          Show all the logged messages on stdout
   --add-host [--add-host option --add-host option]     Add a custom host-to-IP mapping (host:ip)
   --help, -h                                           show help
   --version, -v                                        print the version

% ./zypper-docker update --help     
NAME:
   zypper-docker update - Install the available updates

USAGE:
   zypper-docker update [command options] [arguments...]

OPTIONS:
   -l, --auto-agree-with-licenses       Automatically say yes to third party license confirmation prompt. By using this option, you choose to agree with licenses of all third-party software this command will install.
   --no-recommends                      By default, zypper installs also packages recommended by the requested ones. This option causes the recommended packages to be ignored and only the required ones to be installed.
   --replacefiles                       Install the packages even if they replace files from other, already installed, packages. Default is to treat file conflicts as an error.
   --author "cyphar"                    Commit author to associate with the new layer (e.g., "John Doe <[email protected]>")
   --message "[zypper-docker] update"   Commit message to associated with the new layer

% ./zypper-docker patch-check --help
NAME:
   zypper-docker patch-check - Check for patches

USAGE:
   zypper-docker patch-check [arguments...]

None of those [arguments...] is actually helpful to someone using zypper-docker for the first time.

Attach to the container in order to get both the stdout and the stderr

The current situation is the following:

  • In order to obtain the output from the container, we call the dockerclient.ContainerLogs function.
  • The TTY has to be resized to our liking, as specified in the issue #18

After some investigation, we've realized that we should be using the "Attach to a container" endpoint with either this method or with its websocket version. The problem is that (again...) the dockerclient package does not implement either of these attach methods.

We have also learned how this is done in the Docker client (api/client inside of Docker's source code), and we are going to go to this same route. Basically we have to implement the bare attach endpoint in my personal fork, and then add some extra candy to it as it's done inside of Docker to make it safe and clean.

Here's a checklist:

  • Implement either of the attach endpoints in my personal fork (and send a PR to upstream)
  • Replace the current streaming mechanism with the new attach methods.
  • Cleanup and make everything safer.

Note: this issue invalidates #18 , since we won't need it anymore.

Some tests can write to the host's cache file

Some tests have not defined a specific cache file for testing purposes and are calling a function that will access the cache file. Because of this, some tests end up writing to the cache of the host.

Allow updating of running containers

This might lead to a slippery slope of people treating containers like VMs, but there might be legitimate reasons to do this (you are doing a rolling update, but it takes some time for each node to come up and you don't want all of the remaining nodes to be vulnerable).

We should probably discuss this (and make a proper proposal), but it's an interesting proposal IMO. Another cool thing we could do in that case is implement zypper ps -s for containers and then restart the container.

Build Failing in "make build"

In the corresponding Dockerfile, I am facing the following error:

`Building zypper-docker

docker build -f docker/Dockerfile -t zypper-docker docker

Sending build context to Docker daemon 8.192 kB

Step 1/16 : FROM golang
---> 9ad50708c1cb

Step 2/16 : RUN go get golang.org/x/tools/cmd/cover
---> Using cache
---> 34cf04cf0a71

Step 3/16 : RUN go get golang.org/x/tools/cmd/vet
---> Running in 0c15b5215c5a
package golang.org/x/tools/cmd/vet: cannot find package "golang.org/x/tools/cmd/vet" in any of:
/usr/local/go/src/golang.org/x/tools/cmd/vet (from $GOROOT)
/go/src/golang.org/x/tools/cmd/vet (from $GOPATH)

The command '/bin/sh -c go get golang.org/x/tools/cmd/vet' returned a non-zero code: 1
Makefile:60: recipe for target 'build' failed
make: *** [build] Error 1
`

And when I checked this used at this step, I got "No Such Url exists".

zypper-docker: patch-check exits with wrong code

demo:~ # zypper-docker patch-check opensuse:13.2
Retrieving repository 'NON-OSS' metadata [done]
Building repository 'NON-OSS' cache [done]
Retrieving repository 'OSS' metadata [done]
Building repository 'OSS' cache [done]
Retrieving repository 'OSS Update' metadata [done]
Building repository 'OSS Update' cache [done]
Retrieving repository 'Update Non-Oss' metadata [done]
Building repository 'Update Non-Oss' cache [done]
All repositories have been refreshed.
Loading repository data...
Reading installed packages...
1 patch needed (1 security patch)
demo:~ # echo $?
0

USER directive in Dockerfile breaks zypper-docker

If you have a Dockerfile like this:

FROM opensuse:leap
RUN useradd someuser
USER someuser

Then doing a zypper-docker <command> will cause an error about "must run as root":

Root privileges are required for refreshing system repositories.

Image-CMD deleted after zypper-docker update/patch

After the zypper-docker update oder patch-command, the original CMD of the Image is deleted.
Before:
docker inspect sles12-java8-tomcat8 | grep CMD
"#(nop) CMD ["/bin/sh" "-c" "source /etc/sysconfig/tomcat-default \u0026\u0026 su $TOMCAT_OWNER -c \"$CATALINA_HOME/bin/catalina.sh run \""]"
After:
linux-bqo8:~ # docker inspect sles12-java8-tomcat8-patch_2015-11-11b | grep -b1 -i CMD
"Cmd": [
"zypper --non-interactive ref \u0026\u0026 zypper --non-interactive -n patch"

Instruct travis to perform daily builds

We should use a new (?) Travis feature that allows builds to be triggered on a daily/weekly basis even when no PR are submitted. This would allow us to keep the health status of the project monitored in a better way.

zypper-docker is not cleaning zypper's cache

When applying an update or a patch the final image contains lots of useless data like repositories' metadata and downloaded packages.

Everything should be removed at the end of the process with a call to zypper clean -a.

list-patches: confusing error message when using `--severity` flag

If one runs zypper-docker lp --severity low opensuse:latest, for example, and the host does not have this image, the error message the --severity flag is only available for zypper versions >= 1.12.6 is printed.
It should instead print Error: No such image: opensuse:latest as is done when not using --severity.

Refine how the logging is done

Right now we've got a mixed solution between logging and printing to the standard output. It would be desirable to fix this situation.

migrate to vendor/ experiment

We're not testing nor using Go < 1.5 anymore, therefore we should use the vendor/ experiment which is also supported by Godeps.

Handle signals on the host

zypper-docker should handle singnals. For example: when zypper-docker lu is running a SIGINT signal should be propagated to the container and then to zypper-docker itself.

Add `--severity` flag to `list-patches`

Recent versions of zypper support also the --severity flag to filter the available patches.

Note well: this feature is not available with older releases of zypper, hence we have to handle that properly

Porblem with the demo 'video'

There is a problem with demo video. Almost a minute (from 0:55 til 1:52) I can see only *# it mimics zypper's cli "inte * message and blinking cursor.

The signal handling is too naive

How to reproduce it:

  • zypper-docker images (with lots of images)
  • Hit Ctrl-c to kill the process.

Results: It logs "Shutting down gracefully" but the command goes on.

severity checking for images with zypper < 1.12.6

Hello,

at the moment, there is no way of listing / filtering patches by severity (--severity flag) for images running an older version (< 1.12.6) of zypper. The information regarding the severity however is present when running zypper lp.

If the --severity flag functionality is needed regardless of the version, one has to do some scripting and parse the output of zypper lp which is not the end of the world.

However, it would be possible for zypper-docker to detect an older version (which it already does) and simply deal with it instead of failing (which it currently does). On the other hand, zypper-docker itself is a wrapper around zypper, and introducing flags which aren't actually supported by the zypper version might not be cool.

What are your thoughts on this? Could something go seriously wrong if it were to be implemented?

/cc @inercia @cyphar @flavio @jordimassaguerpla @mssola

Patch/update should be executed once if there was a zypper update

Right now, if in the list of patches/updates to be applied there's at least one concerning to zypper, the update/patching will be done first for zypper and then exit with a specific exit code (thus, not applying other updates/patches that might be available).

zypper-docker should be able to detect this case, and execute patch/update again whenever a zypper update/patch has been applied.

About "USERNAME" in the update command

I think we don't use it, and we use the current user instead. If this is the case, then the value should be removed from flags.go. Otherwise, we should removed "USERNAME" to "ZYPPER_DOCKER_USERNAME".

Packages Outdated

While I was working on issue #108 ,

Commands I used:
make build
make test

The output of make test, was full of errors and the cause was, outdated packages:

go get github.com/codegangsta/cli
go get github.com/coreos/etcd/pkg/fileutil
go get github.com/docker/distribution/reference
go get github.com/docker/docker/client
go get github.com/docker/docker/api/types
go get github.com/docker/engine-api/types/container
go get github.com/docker/engine-api/types/network
go get github.com/docker/engine-api/types/strslice

I've rectified almost all except,

go get github.com/docker/docker/api/client/formatter

Ther error raised is:

images.go:22:2: cannot find package "github.com/docker/docker/api/client/formatter" in any of:
	/usr/lib/go-1.6/src/github.com/docker/docker/api/client/formatter (from $GOROOT)
	/home/nikhil/go/src/github.com/docker/docker/api/client/formatter (from $GOPATH)
Makefile:47: recipe for target 'race' failed
make: *** [race] Error 1
nikhil@nikhil-wicked-machine:~/CONTRIBUTION/opensuse$ images.go:22:2: cannot find package "github.com/docker/docker/api/client/formatter" in any of:
	/usr/lib/go-1.6/src/github.com/docker/docker/api/client/formatter (from $GOROOT)
	/home/nikhil/go/src/github.com/docker/docker/api/client/formatter (from $GOPATH)
No tests found in package '.'.

new release

This is a vote for preparing a new release for zypper-docker.

Since 1.2.0, eleven commits [1] have been merged. Although most are related to fixing the build and CI, there is some user-visible changes related to passing the exit codes from zypper to zypper-docker, so that zypper-docker now returns zyppers exit codes (see bsc#1018823).

As the API has changed regarding exit codes, I suggest to jump to a next major version 2.0.0.

[1] v1.2.0...master

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.