Coder Social home page Coder Social logo

containersolutions / externalsecret-operator Goto Github PK

View Code? Open in Web Editor NEW
189.0 11.0 28.0 84.11 MB

An operator to fetch secrets from cloud services and inject them in Kubernetes

License: Apache License 2.0

Makefile 2.99% Go 96.17% Dockerfile 0.84%
kubernetes cloud-native security aws cloud gcp azure hacktoberfest

externalsecret-operator's Introduction

External Secret Operator

github actions Go Report Card codecov

This operator reads information from a third party service like AWS Secrets Manager or AWS SSM and automatically injects the values as Kubernetes Secrets.

Disclaimer ⚠️

This project will not be maintained anymore, and we are trying to concentrate afforts on this new colaboration:

external-secrets/external-secrets

Website: https://www.external-secrets.io/

Table of Contents

Features

  • Secrets are refreshed from time to time allowing you to rotate secrets in your providers and still keep everything up to date inside your k8s cluster.
  • Change the refresh interval of the secrets to match your needs. You can even make it 10s if you need to debug something (beware of API rate limits).
  • For the AWS Backend we support both simple secrets and binfiles.
  • You can get speciffic versions of the secrets or just get latest versions of them.
  • If you change something in your ExternalSecret CR, the operator will reconcile it (Even if your refresh interval is big).
  • AWS Secret Manager, Credstash (AWS KMS), Azure Key Vault, Google Secret Manager and Gitlab backends supported currently!

Quick start

Using Kustomize

Install the operator CRDs

  • Install CRDs
make install

What does it do?

Given a secret defined in AWS Secrets Manager:

% aws secretsmanager create-secret \
  --name=example-externalsecret-key \
  --secret-string='this string is a secret'

and updated aws credentials to be used in config/credentials/kustomization.yaml with valid AWS credentials:

%cat config/credentials/kustomization.yaml
resources:
# - credentials-gsm.yaml
- credentials-asm.yaml
# - credentials-dummy.yaml
# - credentials-gitlab.yaml
# - credentials-akv.yaml
%cat config/credentials/credentials-asm.yaml
...
credentials.json: |-
    {
      "accessKeyID": "AKIA...",
      "secretAccessKey": "cmFuZG9tS2VZb25Eb2Nz...",
      "sessionToken": "" 
    }

and an SecretStore resource definition like this one:

% cat config/samples/store_v1alpha1_secretstore.yaml
apiVersion: store.externalsecret-operator.container-solutions.com/v1alpha1
kind: SecretStore
metadata:
  name: secretstore-sample
spec:
  controller: staging
  store:
    type: asm
    auth: 
      secretRef: 
        name: externalsecret-operator-credentials-asm
    parameters:
      region: eu-west-2

and an ExternalSecret resource definition like this one:

% cat config/samples/secrets_v1alpha1_externalsecret.yaml
apiVersion: secrets.externalsecret-operator.container-solutions.com/v1alpha1
kind: ExternalSecret
metadata:
  name: externalsecret-sample
spec:
  storeRef: 
    name: externalsecret-operator-secretstore-sample
  data:
    - key: example-externalsecret-key
      version: latest

The operator fetches the secret from AWS Secrets Manager and injects it as a secret:

% make deploy
% kubectl get secret externalsecret-operator-externalsecret-sample -n externalsecret-operator-system \
  -o jsonpath='{.data.example-externalsecret-key}' | base64 -d
this string is a secret

Architecture

In this article you can find more information about the architecture and design choices.

Here's a high-level diagram of how things are put together.

architecture

Running tests

Requirements:

  • Golang 1.15 or later
  • Kubebuilder installed at /usr/local/kubebuilder

Then just:

make test

CRDs Spec

Other Supported Backends

We would like to support as many backends as possible and it should be rather easy to write new ones. Currently supported backends are:

Provider Backend Doc
AWS Secrets Manager Info AWS Secrets Manager Backend Docs
Credstash Info Credstash (AWS KMS) Docs
GCP Secret Manager Info GCP Secret Manager Backend Docs
Gitlab CI/CD Variables Info Gitlab CI/CD Variables Backend Docs
Azure Key Vault Info Azure Key Vault Backend Docs

Contributing

Yay! We welcome and encourage contributions to this project!

See our contributing document and Issues for planned improvements and additions.

externalsecret-operator's People

Contributors

1aziz avatar allanmoso avatar dbirks avatar donrenando avatar frankscholten avatar hack3r-0m avatar iamcaleberic avatar jonathangold avatar knelasevero avatar ng29 avatar paul-the-alien[bot] avatar riccardomc avatar sebagomez avatar shinnlok avatar tbrasser 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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

externalsecret-operator's Issues

Introduce documentation for each backend implementation

We need a consistent way to specify instructions and parameters documentation for each type of backend. We could restructure the secrets directory to contain a sub-directory for each backend with a README.md in it For example:

pkg/secrets/
  asm/
     asm.go
     asm_test.go
     README.md
  onepassword/
     onepassword.go
     onepassword_test.go
     README.md
...

And the README.md would look like something like:

AWS Secrets Manager Backend

Format

Type: "asm"
Name: "asm-example"
Parameters:
    accessKeyID: ""
    region: ""
    secretAccessKey: ""

Parameters

Parameter Description Required Default
accessKeyID AWS Access Key ID that can talk to AWS Secrets Manager yes ""
region AWS Region yes ""
secretAccessKey AWS Secret Access Key corresponding to accessKeyID yes ""

Investigate possibility of refreshing secrets on rotation

Describe the solution you'd like
The ability to define a period after which the secrets with have to be rotated. Reconciliation will have to be triggered and new values pulled.

What is the added value?
Secret values are kept up to date in the cluster.

Give us examples of the outcome

Support for the new refreshInterval field.

apiVersion: external-secrets.k8s.io/v1alpha1
kind: ExternalSecret
metadata: {...}
spec:
  # the amount of time before the values will be read again from the store
  # may be set to zero to fetch and create it once
  refreshInterval: "1h"

Observations (Constraints, Context, etc):

Operator SDK already has a default reconciliation interval time of 10h.

Cannot build image in minikube

Since a few weeks ago I am not able to build the operator image on minikube anymore. Build fails with the following error:

(microdnf:1): libdnf-WARNING **: 14:40:49.067: Skipping refresh of latest-rhubi-7.6: cannot update repo 'latest-rhubi-7.6': Cannot download repomd.xml: Cannot download repodata/repomd.xml: All mirrors were tried; Last error: Curl error (9): Access denied to remote resource for ftp://partners.redhat.com/1c5d859a/UBI-5032b19e400c3605214630e4ba65e14f/7/RHUBI/x86_64/repodata/repomd.xml [Server denied you to change to the given directory]

Introduce namespace restriction

It would be useful to restrict secrets injections to specific namespaces. Possibilities are:

  1. allow/deny regex in ExternalSecretBackend CR definition (depends on ExternalSecretBackend implementation #6 ) in metadata or parameters
  2. allow/deny regex in ExternalSecret CR definition
  3. Annotation in namespaces

1 should give more flexibility.

Integration test fails

[frank@laptop onepassword]$ go test backend_integration_test.go 
Init: Error parsing the OPERATOR_CONFIG env var. unknown backend type: 'onepassword'
E
Errors:

  * /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/secrets/onepassword/backend_integration_test.go 
  Line 28: - runtime error: invalid memory address or nil pointer dereference 
  goroutine 6 [running]:
  panic(0x8d9f80, 0xdc2470)
        /usr/local/go/src/runtime/panic.go:522 +0x1b5
  command-line-arguments.TestOnePasswordBackend.func1.1()
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/secrets/onepassword/backend_integration_test.go:29 +0x51
  github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls.(*ContextManager).SetValues.func1(0x0)
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls/context.go:97 +0x40d
  github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls.EnsureGoroutineId(0xc000127860)
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls/gid.go:19 +0x108
  github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls.(*ContextManager).SetValues(0xc000073aa0, 0xc000127800, 0xc0000
0f7c0)
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls/context.go:63 +0x14b
  command-line-arguments.TestOnePasswordBackend.func1()
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/secrets/onepassword/backend_integration_test.go:28 +0x169
  github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls.(*ContextManager).SetValues.func1(0x0)
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls/context.go:97 +0x40d
  github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls.EnsureGoroutineId.func1()
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls/gid.go:24 +0x2e
  github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls._m(0x0, 0xc00000f620)
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls/stack_tags.go:108 +0x31
  github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls.github_com_jtolds_gls_markS(0x0, 0xc00000f620)
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls/stack_tags.go:56 +0x35
  github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls.addStackTag(...)
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls/stack_tags.go:49
  github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls.EnsureGoroutineId(0xc0001275c0)
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls/gid.go:24 +0xd6
  github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls.(*ContextManager).SetValues(0xc000073aa0, 0xc000127560, 0xc00000f5e0)
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/github.com/jtolds/gls/context.go:63 +0x14b
  command-line-arguments.TestOnePasswordBackend(0xc000134100)
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/secrets/onepassword/backend_integration_test.go:16 +0xcd
  testing.tRunner(0xc000134100, 0x97cf38)
        /usr/local/go/src/testing/testing.go:865 +0xc0
  created by testing.(*T).Run
        /usr/local/go/src/testing/testing.go:916 +0x35a
  
  goroutine 1 [chan receive]:
  testing.(*T).Run(0xc000134100, 0x95f63c, 0x16, 0x97cf38, 0x47baf6)
        /usr/local/go/src/testing/testing.go:917 +0x381
  testing.runTests.func1(0xc000134000)
        /usr/local/go/src/testing/testing.go:1157 +0x78
  testing.tRunner(0xc000134000, 0xc0000bde30)
        /usr/local/go/src/testing/testing.go:865 +0xc0
  testing.runTests(0xc00000f580, 0xdc3450, 0x1, 0x1, 0x0)
        /usr/local/go/src/testing/testing.go:1155 +0x2a9
  testing.(*M).Run(0xc0000dc400, 0x0)
        /usr/local/go/src/testing/testing.go:1072 +0x162
  main.main()
        _testmain.go:42 +0x13e
  
  goroutine 5 [chan receive]:
  github.com/ContainerSolutions/externalsecret-operator/vendor/k8s.io/klog.(*loggingT).flushDaemon(0xdd06a0)
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/k8s.io/klog/klog.go:943 +0x8b
  created by github.com/ContainerSolutions/externalsecret-operator/vendor/k8s.io/klog.init.0
        /home/frank/go/src/github.com/ContainerSolutions/externalsecret-operator/vendor/k8s.io/klog/klog.go:403 +0x6c
  


1 total assertion

--- FAIL: TestOnePasswordBackend (0.00s)
FAIL
FAIL    command-line-arguments  0.005s
[frank@laptop onepassword]$ ```

Doesn't build on macOS

having issues building on macos

Running make
pulls dependencies, but at the end throws:

Step 9/12 : RUN gpg --keyserver hkp://keys.gnupg.net --recv-keys 3FEF9748469ADBE15DA7CA80AC2D62742012EA22
 ---> Running in a98a36ee315c
gpg: directory `/root/.gnupg' created
gpg: new configuration file `/root/.gnupg/gpg.conf' created
gpg: WARNING: options in `/root/.gnupg/gpg.conf' are not yet active during this run
gpg: keyring `/root/.gnupg/secring.gpg' created
gpg: keyring `/root/.gnupg/pubring.gpg' created
gpg: requesting key 2012EA22 from hkp server keys.gnupg.net
gpg: no valid OpenPGP data found.
gpg: Total number processed: 0
gpgkeys: key 3FEF9748469ADBE15DA7CA80AC2D62742012EA22 can't be retrieved
The command '/bin/sh -c gpg --keyserver hkp://keys.gnupg.net --recv-keys 3FEF9748469ADBE15DA7CA80AC2D62742012EA22' returned a non-zero code: 2
Error: failed to output build image containersol/externalsecret-operator: (failed to exec []string{"docker", "build", "-f", "build/Dockerfile", "-t", "containersol/externalsecret-operator", "."}: exit status 2)
Usage:
  operator-sdk build <image> [flags]

Flags:
      --go-build-args string      Extra Go build arguments as one string such as "-ldflags -X=main.xyz=abc"
  -h, --help                      help for build
      --image-build-args string   Extra image build arguments as one string such as "--build-arg https_proxy=$https_proxy"
      --image-builder string      Tool to build OCI images. One of: [docker, podman, buildah] (default "docker")

Global Flags:
      --verbose   Enable verbose logging

make: *** [build] Error 1

Reboot the project as ExternalSecrets-Operator

After some thought and discussions I've decided that is better to descope configuration for the time being and focus on Secrets.

This will allow the project to tackle Secrets first without handling the complexity of ConfigMaps.

SIGSEGV when signing in

[frank@laptop externalsecret-operator]$ kubectl logs externalsecret-operator-66c65b9f78-ktm59 
{"level":"info","ts":1560770908.9121733,"logger":"cmd","msg":"Go Version: go1.11"}
{"level":"info","ts":1560770908.9122849,"logger":"cmd","msg":"Go OS/Arch: linux/amd64"}
{"level":"info","ts":1560770908.912331,"logger":"cmd","msg":"Version of operator-sdk: v0.6.0+git"}
{"level":"info","ts":1560770908.9123917,"logger":"cmd","msg":"External Secret Operator Version: 0.0.3"}
{"level":"info","ts":1560770908.9124296,"logger":"backend","msg":"initFromEnv","availableBackends":"asm,dummy,onepassword"}
{"level":"info","ts":1560770908.9125664,"logger":"backend","msg":"instantiate","name":"onepassword","type":"onepassword"}
{"level":"info","ts":1560770908.9126704,"logger":"backend","msg":"initialize","name":"onepassword"}
Signing into 1password.
Started '/usr/local/bin/op'.
Enter the Secret Key for [email protected] at externalsecretoperator.1password.com: panic: runtime error
: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x45bf80]

Improve go report card score

https://goreportcard.com/report/github.com/ContainerSolutions/externalsecret-operator

Gofmt formats Go programs. We run gofmt -s on your code, where -s is for the "simplify" command

        externalsecret-operator/secrets/asm/backend_test.go
        Line 1: warning: file is not gofmted with -s (gofmt)


Golint is a linter for Go source code.

        externalsecret-operator/secrets/onepassword/client.go
        Line 10: warning: exported type Client should have comment or be unexported (golint)
        Line 15: warning: exported type OP should have comment or be unexported (golint)
        Line 19: warning: exported method OP.SignIn should have comment or be unexported (golint)
        Line 30: warning: exported method OP.Get should have comment or be unexported (golint)


IneffAssign detects ineffectual assignments in Go code.

        externalsecret-operator/secrets/backend/backend.go
        Line 72: warning: ineffectual assignment to err (ineffassign)


Misspell Finds commonly misspelled English words

        externalsecret-operator/secrets/backend/backend_test.go
        Line 92: warning: "unkown" is a misspelling of "unknown" (misspell)

        externalsecret-operator/secrets/asm/backend.go
        Line 79: warning: "paramters" is a misspelling of "parameters" (misspell)

        externalsecret-operator/secrets/asm/backend_test.go
        Line 79: warning: "paramters" is a misspelling of "parameters" (misspell)
        Line 90: warning: "paramters" is a misspelling of "parameters" (misspell)
        Line 102: warning: "paramters" is a misspelling of "parameters" (misspell)

Feature Proposal: Git Backend (would mean some Keybase.io support too)

Feature Proposal: Git Backend (also Keybase.io)

General

Keybase.io provides end-to-end encrypted shared filesystem and Git repositories.

The service they provide also requires no infrastructure other than the client setup.

A Keybase backend would be able to read secrets from a Keybase filesystem and/or a Keybase repo, either from an individual or team account.

Keybase Git repos are better for secrets (IMHO) since Git has change history and repos are not as easy to mistakenly delete.

Git repos also have branches. This could be utilised to provide separate secrets per environment.

Keybase Git Repos are regular Git repos, with a keybase:// protocol.

So implementing a Git backend, would also add support for Keybase repos.

Authentication to Keybase

Keybase client requires username and a "paper key" for keybase git helper to work.
Can be specified in environment variables:

$ keybase oneshot
OPTIONS:
   --paperkey 		DANGEROUS: specify a paper key (or try the KEYBASE_PAPERKEY environment variable)
   -u, --username 	specify a username (or try the KEYBASE_USERNAME environment variable)

In case of keybase:// or any other repo source, the operator would be responsible for setting up a deployment Docker image with Keybase support (or any other authentication like SSH)

Implementation

CRD example

% cat deploy/crds/externalsecret-operator_v1alpha1_externalsecret_cr.yaml
apiVersion: externalsecret-operator.container-solutions.com/v1alpha1
kind: ExternalSecret
metadata:
  name: example-externalsecret
spec:
  key: /path/in/repo/my-secret.yml
  backend: git

The key is a path to a file from the target repo. The operator reads the file and creates a K8 secret with the value.

The operator

There is a pure Golang Git client, https://github.com/src-d/go-git.
That client can checkout repos in memory, so no storage required.

Helm deployment test is broken

Helm deployment test i currently not passing because of operatorName not being applied correctly as OPERATOR_CONFIG environment variable in the deployment.

It is currently set to the release full name, but it shoud be operatorName when set and release short name in case operatorName is not set.

This will allow us to have a single name across:

  1. helm release
  2. operator name
  3. backend name

while still leaving the possiblity to set a different name between helm release and operator name/backend name.

Support hashicorp vault?

Describe the solution you'd like
Support vault as a backend for secrets.

What is the added value?
Having new backends increases chances for adoption and broadens the operator use cases.

Give us examples of the outcome


apiVersion: store.externalsecret-operator.container-solutions.com/v1alpha1
kind: SecretStore
metadata:
  name: secretstore-sample
spec:
  controller: staging
  # Sample store types
  #
  # Vault  
  store:
    type: vault
    auth: 
      secretRef: 
        name: externalsecret-operator-credentials-vault
        namespace: externalsecret-operator-system
    parameters:
      server: vault.example.com
      path: /vault/path

Observations (Constraints, Context, etc):

Add 1password backend

  • Create a stub backend and unit test
  • Stub backend should return static JSON response from 1password
  • Add op invocation
  • Add op to Dockerfile
  • Deploy and test operator
  • Create scripts for creating, sourcing and deleting kubernetes secrets for the 1password account
  • Add deployment with secrets
  • Parse secrets from environment to sign in to 1password
  • Implement SignIn for OnePasswordCliClient

Introduce Secret Backends implementation and CRDs

It is possible to specify a Backend attribute in an ExternalSecret resource, however this value is ignored.

In fact, the AWS Secret Manager backend is configured using environment variable.

We could introduce an ExternalSecretBackend CRD that helps configure multiple backends. For example:

apiVersion: "externalsecret-operator.container-solutions.com/v1alpha1"
kind: "ExternalSecretBackend"
metadata:
  name: "production-secrets"
spec:
  BackendType: "asm"
  AccessKeyID: "AKIAIOSFODNN7EXAMPLE"
  SecretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

And then reference it from the ExternalSecret resources as in:

apiVersion: "externalsecret-operator.container-solutions.com/v1alpha1"
kind: "ExternalSecret"
metadata:
  name: "asecret"
spec:
  Key: "asecret"
  Backend: "production-secrets"

Unify backend name and operator name

Currently there are two different names for the operator and the backend that it is instantiated.

This is because we initially planned to support multiple backends per operator and the backend name would be visible in an ExternalSecretBackend CRD.

Since we now have a 1-1 relationship between backends and operators, this distinction is no longer necessary. However the backend name is still important to distinguish which backend to use and is required in ExternalSecret CRDs.

The main issue is now that the backend name is held in the configuration held in a Secret and is not easily visible. A possible solution would be to use the OPERATOR_NAME environment variable.

Deploy on internal cluster

  • Rename 1Password accounts
  • Introduce naming convention for 1Password accounts and document
  • Deploy the operator
  • Fix "Cannot find backend" error

Introduce end to end testing

Currently only a small set of unit tests are included. We should have some tests that actually verify the creation of the secrets in a real K8S cluster.

Figure out how to do releases

  • Check how other operators handle releases
  • Generate release report
  • Proper versioning
  • Proper image tagging
  • Push to repo

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.