Coder Social home page Coder Social logo

peopledoc / vault-cli Goto Github PK

View Code? Open in Web Editor NEW
80.0 48.0 20.0 839 KB

A configurable command-line interface tool (and python library) to interact with Hashicorp Vault

Home Page: https://vault-cli.readthedocs.io/

License: Other

Python 99.59% Shell 0.41%
tribe-python hashicorp-vault peopledoc-opensource ghec-mig-migrated approved-public

vault-cli's Introduction

vault-cli: 12-factor oriented command line tool for Hashicorp Vault

Deployed to PyPI Documentation Status Continuous Integration Status Coverage Status Apache License Contributor Covenant

vault-cli is a Python 3.6+ tool that offers simple interactions to manipulate secrets from Hashicorp Vault. With vault-cli, your secrets can be kept secret, while following 12-factor principles.

Some features

  • Configure once, use everywhere thanks to cascading (local, user, global) YAML configuration file
  • Read, browse, write, move, delete secrets easily
  • Read multiple secrets at once, as YAML
  • Launch processes with your secrets as environment variables
  • Launch processes with ssh-agent configured from your vault
  • Write templated files with secrets inside

vault-cli tries to make accessing secrets both secure and painless.

Showcase

Here are a few things you might do with vault-cli:

$ # Install:
$ pip install vault-cli

$ # Write a secret:
$ vault-cli set mysecret mykey --prompt
Please enter a value for key `mykey` of `mysecret`: *******

$ # Read a secret:
$ vault-cli get mysecret mykey
ohsosecret

$ # Load a secret into the environment variables:
$ vault-cli env --envvar mysecret -- env | grep MYSECRET
MYSECRET_MYKEY=ohsosecret

$ # Load an ssh key into your ssh-agent:
$ vault-cli ssh --key ssh_private_key -- ssh -T [email protected]
Hi <username>! You've successfully authenticated, but GitHub does not provide shell access.

State

The package is young but supported and alive. We're mindful of deprecations through semantic versionning and accepting bug reports and feature requests.

Where to go from here

The complete docs is probably the best place to learn about the project.

If you encounter a bug, or want to get in touch, you're always welcome to open a ticket.

vault-cli's People

Contributors

brunobord avatar ewjoachim avatar irvansemestanya avatar jacquesrott avatar mgu avatar pierreducroquet avatar pilou- avatar sdgjlbl avatar sheb avatar soualid avatar weakcamel avatar yannlachiver 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

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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vault-cli's Issues

Prompt user with hidden input so the secrets won't appear in the shell history

Rationale

Using vault-cli without caution results in the full display of the following command in the shell history:

vault set foo bar

The "bar" secret is absolutely visible in plain sight, which is... not very secure.

The only way to prevent this at the moment is to add a space at the beginning of command line, but that looks like the "poor man protection". Disadvantages of this method:

  1. It is only true if your shell has the following environment variable: HISTCONTROL=ignorespace.
  2. if you messed up with your command, you have to retype it entirely
  3. It doesn't prevent the "person looking over your shoulder" flaw, because the secret is displayed on the screen.

Solution

Using click.prompt() with the argument hide_input=True, it's possible to prompt the user for a value without showing the user input in the history. It's "clipboard" and history friendly enough.
The only it doesn't prevent is the usage of a keylogger. But then, vault-cli can't probably do something for you about that.

PR is on its way, hopefully.

Vault set should make additional checks for templates

Hello 👋

Just found it the hard way:
A template containing invalid jinja is saved, thus breaking most vault-cli usages

vault set django/TOTO_TEST value='!template!username={{ vault(\"quotes_issues\").username }}'

$ vault env --path my=path -- env
[...]
jinja2.exceptions.TemplateSyntaxError: unexpected char '\\' at 18
# no var are injected
$ vault get-all

[...]
jinja2.exceptions.TemplateSyntaxError: unexpected char '\\' at 18

same with templates containing invalid references

$ vault set django/TOTO_TEST value='!template!username={{ vault("wont/find/me").username }}'
$ vault env --path my=path -- env
[...]
Error: VaultRenderTemplateError: Error while rendering template: 'wont/find/me' not found

$ vault get-all
# no traceback nor vars
Error: VaultRenderTemplateError: Error while rendering template: 'wont/find/me' not found

Should we evaluate templates before saving them into vault (and catch these kind of errors early)?
or should vault-cli not fail in those cases?

[Vault_cli][SSH] ssh-add message

Bonjour à tous,

lors de l'utilisation de vault_cli avec l'option ssh, le message de ssh-add s'affiche.

/usr/local/bin/vault --config-file /etc/{SERVICE}/vault_ro.yml ssh --key {PATH}:{VALUE}

Identity added: (stdin) ((stdin))

Est-il possible de rendre cette affichage optionnel afin de pouvoir le désactiver (notamment lors de l'utilisation via cron) ?

Improve error management

Standard errors, independent from the backend used, for usual errors, caught with the proper CLI display.

For unexpected errors, for now, it's ok to raise the backend expections.

Expected errors include:

  • Incorrect auth
  • Secret not found
  • Vault sealed
  • ... any idea ?

Add cache

Make it so that when using as CLI, a same secret won't be read twice.

When using as lib, a context manager will allow you to have the same behaviour.

add support for templates in dict

Currently the "template" feature of vault-cli only works when the value is a string ({"value": "!template!..."}

We would like to also support templates when values are dict. For example:

{
  "value": {
    "a": "!template!...",
    "b": "!template!...",
    "c": "value",
  }
}

Move secret

vault mv, recursive.

  • Only takes 2 args
  • If the 2nd argument is an existing secret, fail
  • vault mv path1 path2 will result in:
    • path1 will be recursively read
    • the read value(s) will be written in path2/{basename(path1)}
    • path1 will be recursively deleted

"vault-cli get" doesn't return YAML formatted value when secret isn't a string

vault-cli get doesn't return YAML formatted value when secret isn't a string.

When secret value isn't a string, output of vault get secret should be the same as output of vault get --yaml secret.

Reproducer1 (tested with Python 3.6.8)

$ cat test.json 
{
  "foo": "bar",
  "train": [1,2,3,4],
  "GNU": {
    "Linux": {
      "Debian": "Buster"
    }
  }
}
$ cat test.json | vault set --stdin secret
Done
$ vault get secret
{
  "foo": "bar",
  "train": [1,2,3,4],
  "GNU": {
    "Linux": {
      "Debian": "Buster"
    }
  }
}
$ vault get --text secret
{
  "foo": "bar",
  "train": [1,2,3,4],
  "GNU": {
    "Linux": {
      "Debian": "Buster"
    }
  }
}
$ vault get --yaml secret
--- "{\n  \"foo\": \"bar\",\n  \"train\": [1,2,3,4],\n  \"GNU\": {\n    \"Linux\"\
  : {\n      \"Debian\": \"Buster\"\n    }\n  }\n}"

Reproducer2 (tested with Python 3.6.8)

$ cat test.json 
{
  "value": {
    "foo": "bar",
    "train": [1,2,3,4],
    "GNU": {
      "Linux": {
        "Debian": "Buster"
      }
    }
  }
}
$ cat test.json | vault kv put secret/testproject/secret value=- # "official" vault binary used here
$ vault kv get -format=yaml secret/testproject/secret # official vault binary used here
data:
  value: |
    {
      "value": {
        "foo": "bar",
        "train": [1,2,3,4],
        "GNU": {
          "Linux": {
            "Debian": "Buster"
          }
        }
      }
    }
$ vault get secret #  vault-cli binary used here
{
  "value": {
    "foo": "bar",
    "train": [1,2,3,4],
    "GNU": {
      "Linux": {
        "Debian": "Buster"
      }
    }
  }
}
$ vault get --text secret # vault-cli binary used here
{
  "value": {
    "foo": "bar",
    "train": [1,2,3,4],
    "GNU": {
      "Linux": {
        "Debian": "Buster"
      }
    }
  }
}
$ vault get --yaml secret  # vault-cli binary used here
--- "{\n  \"value\": {\n    \"foo\": \"bar\",\n    \"train\": [1,2,3,4],\n    \"GNU\"\
  : {\n      \"Linux\": {\n        \"Debian\": \"Buster\"\n      }\n    }\n  }\n}\n"

It looks like force_yaml is always false (secret is always a string).

base-path difference when using trailing slash or not

When configuring your vault.yml, using a trailing or not will have different results.

$ vault --base-path=base/ get test
foo

While

$ vault --base-path=base get test
vault_cli.vault_python_api.VaultAPIException: status=403 error="permission denied"

When used as lib, need to close the connection

We're getting this error:

ResourceWarning: unclosed <socket.socket fd=10, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('x.x.x.x', 53920), raddr=('x.x.x.x', 8200)>

The session should be available as a context manager, to ensure it's properly closed.

vault-cli installed as vault, causes collision with official hashicorp vault cli

The documentation says to install vault-cli with pip install vault-cli. However, the script is available as vault and because ~/.local/bin comes first in my path, it is overriding the vault CLI binary, making it impossible to use both tools. Steps to reproduce (using pipx):

# pipx install vault-cli

# pipx list
venvs are in ~/.local/pipx/venvs
apps are exposed on your $PATH at ~/.local/bin
   package vault-cli 1.2.0, Python 3.8.2
    - vault

# ls -al ~/.local/bin
total 0
drwxr-xr-x  3 user  staff   96 Apr  8 10:51 .
drwx------  5 user  staff  160 Apr  8 10:51 ..
lrwxr-xr-x  1 user  staff   51 Apr  8 10:51 vault -> /Users/user/.local/pipx/venvs/vault-cli/bin/vault

Why is this the case? Also, can this be reverted?

Add a `--config-file`

It could be used to force not using a config file (--config-file=no), or to use a specific one that is not in a standard location (--config-file=my.cfg.yml)

Support LDAP Authentication

I've read the documentation and looked through the code but I don't see a way to set the authentication to use the ldap method. Is this supported? It might simply be a tweak to the authentication mechanism to grab a token to use a different body and url.

The curl command to authenticate with LDAP looks like this:

VAULT_TOKEN=$(curl -s -S \
    --request POST \
    --header "Accept: application/json" \
    --header "Content-Type: application/json" \
    --header "X-Vault-Namespace: $NAMESPACE" \
    --data @<(/usr/bin/cat <<< "{\"password\":\"$VAULT_PASSWORD\"}") \
    "$VAULT_URL/v1/auth/ldap/login/$USERNAME" \
    | jq -j .auth.client_token)

Using the include directive in vault template

vault template command crashes with a stacktrace when you try to use it against a Jinja2 template file which contains include, for example:

file1:

foo {% include 'file2' %} bar

file2:

whatever

The problematic code is here: https://github.com/peopledoc/vault-cli/blob/master/vault_cli/client.py#L465

From https://jinja.palletsprojects.com/en/2.10.x/api/#jinja2.Template

Normally the template object is generated from an Environment but it also has a constructor that makes it possible to create a template instance directly using the constructor. It takes the same arguments as the environment constructor but it’s not possible to specify a loader.

See a discussion on similar subject here: https://stackoverflow.com/questions/39288706/jinja2-load-template-from-string-typeerror-no-loader-for-this-environment-spec

Sample stacktrace:

$ vault --token-file=~/.vault-token template config.yml.j2
Traceback (most recent call last):
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/bin/vault", line 10, in <module>
    sys.exit(main())
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/vault_cli/cli.py", line 500, in main
    return cli()
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/click/core.py", line 1137, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/click/decorators.py", line 27, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/contextlib.py", line 74, in inner
    return func(*args, **kwds)
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/vault_cli/cli.py", line 476, in template
    result = client_obj.render_template(template.read())
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/vault_cli/client.py", line 74, in wrapper
    return method(self, *args, **kwargs)
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/vault_cli/client.py", line 465, in render_template
    return jinja2.Template(template).render(vault=vault)
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/jinja2/asyncsupport.py", line 76, in render
    return original_render(self, *args, **kwargs)
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/jinja2/environment.py", line 1008, in render
    return self.environment.handle_exception(exc_info, True)
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/jinja2/environment.py", line 780, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/foouser/.local/share/virtualenvs/config-8gAdPw7v/lib/python3.7/site-packages/jinja2/_compat.py", line 37, in reraise
    raise value.with_traceback(tb)
  File "<template>", line 11, in top-level template code
TypeError: no loader for this environment specified

SyntaxError on every command

tlehoux@mymachine:~$ vault list
Traceback (most recent call last):
  File "/usr/local/bin/vault", line 7, in <module>
    from vault_cli.cli import main
  File "/var/<somewhere>/vault_cli/lib/python3.5/site-packages/vault_cli/__init__.py", line 19, in <module>
    from vault_cli.client import VaultAPIException, get_client
  File "/var/<somewhere>/vault_cli/lib/python3.5/site-packages/vault_cli/client.py", line 89
    client_class: Type[VaultClientBase]
                ^
SyntaxError: invalid syntax
tlehoux@mymachine:~$ cat /etc/debian_version
9.4

the python version

Python 2.7.15 (default, Jun 17 2018, 12:46:58)
[GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.``` 

As a result, no more release is possible for our project. 

Don't mess with new lines too much

ping @tyoras

  • When a secret doesn't contain a new line, it's probably good to strip it from all whitespace characters before saving it, and when displaying it with --text, we can add a trailing new line
  • When a secret contains a newline (let's say it's a multi-line secret), we should not mess with it: no stripping input, and displaying as-is.

Need:

  • There are tools that don't like multi-line secrets (a.k.a certs) that don't have a final new line.

vault patch

$ vault patch /etc/someprogram/conf.py "^ +api_token: ?(.*)$" some/path [key]

Will read the following file:

[someprogram]
    url: someprogram.example.com
    api_token: old token

and will read the secret at some/path (say, new token) and rewrite the file as follow:

[someprogram]
    url: someprogram.example.com
    api_token: new token

For now we do not plan to have multiple replacements or handle templates. Maybe someday. Meanwhile, a templated secret can be used as an intermediate step to do that.

Simply select umask

Multiple options of vault-cli allow creating a file. It would be nice if we could select the umask used by the process for creating files, thous would allow for a simpler integration.

Vault template

vault set secret/path mysecretvalue

# /etc/something.conf.j2
mysecret={{ vault("secret/path") }}

vault template /etc/something.conf.j2 > /etc/something/conf

Results in

# /etc/something.conf
mysecret=mysecretvalue

vault-cli tree or vault-cli list --recursive

There should be a way to recusively display the secret tree without displaying actual secret values. We should display the mapping keys of the secret too (maybe with a flag)

The name "vault" might be ambiguous

Honestly, I'm not sure about this issue raised by @mgu (I'm naming names here :D), because "this ship has sailed". I'm -1 to removing the name vault by default but I'm absolutely not opposed to having another name in use too, and maybe ok having a mechanism to disable the creation of the vault entrypoint, but only if it's very explicit.

Id go so far as to say this: someone could always register another package on PyPI which would either repackage the same code and change the entrypoint or depend on vault-cli and neutralize the entrypoint. I'm not saying it would be a good idea, though, but I can't keep them from doing this.

In this issue, we can discuss this matter. If everyone else is convinced that vault is a bad name compared to <whatever>, then it's easy to keep both names for 6 months with a deprecation warning, so I'll be following along.

Feature request: bootstrap-env

The idea would be:

$ vault bootstrap-env path1/ path2/ some_secret -- some command

Would read all secrets from path1, path2 and some_secret, put them in the environment, and then exec some command.

A --backup /some/location would make it so that a yaml file with those secrets would be written locally at that location. If the vault is not available and the backup file is, then the backup file is used transparently and the command runs (though with a logger.exception).

Use case: start an app that reads settings from the environment (this would be the systemd entrypoint). The exec part is important so that we don't have to manage signals or what, the process is replaced with the real process as soon as possible.

The --backup flag would allow to use this way of starting the app even if your vault instance doesn't have 100% high availability yet (if the app restarts when your vault is down, your app can still run)

Feature request - follow path

The vault-cli handles "special" secrets as a "path" to read.

For example, the vault-cli behavior expected would be:

  • get the secret value rabbitmq-front/creds/shared from the path app/infra/rabbit/front-shared/engine_path
  • read and return the secret value from this path rabbitmq-front/creds/shared (in this example the secret value is a json dict {"username": "xx", "password": "xx"}

ie. naive implementation: this behavior occurs when a path matches /engine_path$

This is needed to integrate RabbitMQ secret Engine 'seamlessly' for apps (https://www.vaultproject.io/docs/secrets/rabbitmq/index.html)

cc @mgu @yannlachiver @pilou- @marieluce-allee @ccouturi @damienbertau

vault template read templates from command line

If we can make it easier to provide the template in the command line, that might make it really easy to do tasks like "write a given secret on a file on the disk".

Use case might be "writing nginx certificate at the start of the process in systemd's PreExecStart"

Can't read values from external (non vault-cli) sources

vault-cli has been designed read and write all the values in a "value" key.

With the kv1 secret engine, vault set a b will put this kind of secret in the vault :

data:
  value: b

But some values from external sources (e.g. the rabbitmq secret engine) have this kind of secret :

data:
  username: foo
  password: bar

The current version (0.8.0) can't read such values (and fails with a KeyError).
It would be great to handle such values (in get/get-all operations) and return the "data" dict directly

Create a self-test command

It would do a set, a list and get, and finally a delete and return with exit code zero if it all went well

Parse error when setting a variable

I'm getting en error Error: no such option: -----BEGIN PRIVATE KEY----- when trying to set a variable. I'm guessing it's because it's trying to parse a string starting with ----- as some option to the script.

To reproduce:
vault set sae_private_crt_key "-----asd"

Support mv operation in v2 of kv secrets engine

I am not sure if vault-cli is expected to work with v2 of kv in general since I had to add the 'data/' in the secret path myself to fetch secrets with the 'get' subcommand. With 'mv' subcommand I was not so lucky though. Here is the debug output.

$ vault -vv mv kv/data/path/to/secret kv/data/new/path/to/secret
INFO:vault_cli.cli:Log level set to DEBUG
INFO:vault_cli.settings:Reading yaml config file at ./vault.yml, contains keys: token, url, verify
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): vaultserver.example.org:8200
DEBUG:urllib3.connectionpool:https://vaultserver.example.org:8200 "GET /v1/kv/data/path/to/secret?list=True HTTP/1.1" 405 64
DEBUG:urllib3.connectionpool:https://vaultserver.example.org:8200 "GET /v1/kv/data/path/to/secret HTTP/1.1" 200 410
Move 'kv/data/path/to/secret' to 'kv/data/new/path/to/secret'
DEBUG:urllib3.connectionpool:https://vaultserver.example.org:8200 "GET /v1/kv/data/new/path/to/secret HTTP/1.1" 200 312
DEBUG:urllib3.connectionpool:https://vaultserver.example.org:8200 "GET /v1/kv/data/new/path/to/secret?list=True HTTP/1.1" 405 64
Error: Unexpected vault error

The List operation of Vault API should be done differently (https://www.vaultproject.io/api/secret/kv/kv-v2.html#list-secrets) in version 2 of kv secrets engine, so I guess the problem has to do with this.

--verify should be --verify-certificate

It's not obvious that --verify is about the certificate, it could be named ``--verify-certificate`. Though, it would be right to keep the old one forever for compatibility.

Add Readme on certs folder and test_integration to notify about test-only certs and keys

A readme file should be added to the certs folders in the repo to clarify that certs and keys are for testing purposes only. This also helps avoiding false positives on future assessments of the repo.

In addition, similar should be put on the test_integrations.py script for the section that shows an SSH private key in use, to clearly note that it is for testing purposes and need to be replaced by the user with a valid key.

Remove the hard-coded "value" key used in get/set

The kv engine of Vault stores key/value dicts but the current version of vault-cli forces a hard-coded key named value. This way the user thinks that there is a "single value" associated with a path (instead of a dict).

Unfortunately this is a limitation for some use-cases (use of other engines, work with values set with another tool than vault-cli, ...).

Here is a proposal for dropping this hard-coded key and working with k/v mappings instead.

# 
# SET
#

# overwrite the whole mapping
vault set path/to/creds key1=val1 key2=val2

# Using a file (json/yaml)
vault set path/to/creds --file=/path/to/file.yml
echo '{"key1":"val1", "key2": "val2"}' | vault set path/to/creds --file=-

# updating a mapping (could be the default ?!)
vault set --update path/to/creds key3=val3

# reading a (string) value from stdin
vault set path/to/creds key1=val1 key2=- < /path/to/val2
# if you have multiple files to read then you'll have to run the command multiple times with --update

#
# GET
#
vault get path/to/creds key1  # reads the value of key1
vault get path/to/creds # no key specified, returns a mapping in json

# 
# DELETE
# 
vault delete path/to/creds  # delete the mapping
vault delete path/to/creds key1  # only delete key1

Remove requests backend and use hvac by default

We have new features (add authentication method, use specific secret engine, ...) that could impact the different backends.

If we want to make our life easier, it would be simpler to only have one backend.
Do you agree on using hvac by default and removing the requests backend ?

Expose a Python API

Make the tool usable as a python lib too, so that one can import vault and call equivalent apps without creating a subprocess, and still get the benefits from the /etc/ config file

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.