Code Monkey home page Code Monkey logo

snitchdns's Introduction

Caution

This repo is unmaintained, visit https://github.com/sadreck/SnitchDNS for the latest version.

SnitchDNS

SnitchDNS is a database driven DNS Server with a Web UI, written in Python and Twisted, that makes DNS administration easier with all configuration changed applied instantly without restarting any system services.

One of its main features is the logging of all DNS queries allowing the discovery of network traffic endpoints, and it can also be used to implement canary tokens as it supports notifications via e-mail, web push, Slack, and Teams. Red teamers can also use SnitchDNS to monitor phishing domains for sandboxes, integrate with SIEM solutions, restrict responses to specific IP ranges, egress data via a DNS tunnel and catch-all domains, and more.

Dependencies

  • Python 3.6+

Installation

Please make sure you install using git rather than by downloading the repo manually.

Documentation

For general documentation see here

Screenshots

For screenshots see here

Basic Features

  • Database Driven.
    • Changes are reflected immediately on each DNS request.
    • Supported DBMS:
      • SQLite
      • MySQL / MariaDB
      • Postgres
  • DNS Server
    • Support for common DNS Records.
      • A, AAAA, AFSDB, CNAME, DNAME, HINFO, MX, NAPTR, NS, PTR, RP, SOA, SPF, SRV, SSHFP, TSIG, TXT.
    • Catch-All Domains.
      • Ability to match any subdomain (no matter the depth) to a specific parent domain, for instance *.hello.example.com.
    • Unmatched Record Forwarding.
      • Functionality to intercept specific queries (ie only A and CNAME) and forward all other records to a third-party DNS server (ie Google).
    • Regular Expression matching.
  • Tags and Aliases.
  • IP Rules
    • Configure Allow/Block rules per domain.
  • Notifications. Receive a notification when a domain is resolved, via:
    • E-mail
    • Web Push
    • Slack
    • Microsoft Teams
  • User Management
    • Multi-User support
      • Each user is given their own subdomain to use.
    • LDAP/RADIUS Support
    • Two Factor Authentication
    • Password Complexity Management
  • Logging
    • All DNS queries are logged, whether they have been matched or not.
    • CSV Logging for SIEM integration.
  • Swagger 2.0 API
  • Deployment
    • Ansible scripts for Ubuntu 18.04 / 20.04
    • Docker
    • CLI support for zone, record, user, and settings management.
    • CSV Export/Import

Use Cases

SnitchDNS can be used for:

  • A DNS Forwarding Server - Allowing you to monitor all requests via a Web GUI.
  • Red Teams - Implement IP restrictions to block sandboxes, monitor phishing domain resolutions and e-mails, and restrict access to known IP ranges.
  • DNS Tunnel - Log all DNS requests and egress data.
  • Let's Encrypt DNS Challenge, using the API or the CLI interface.
  • Ad-blocking.
  • Canary Tokens.
  • Integrate with SIEM solutions.

For more details on scenarios please see the Use Cases Document

Limitations

  • SnitchDNS currently runs in a single-thread, therefore may not be suitable for environments with hundreds of DNS requests per minute.

Contributing

If you wish to contribute pull requests, feature requests, and bug reports - feel free to raise an issue (especially before you start writing code).

Security

If you identify any security vulnerabilities within SnitchDNS, for the time being please contact me on twitter - @sadreck

Credits

UI

Development

  • Lambros Zannettos | @_C960_ - For his help with writing the Dockerfile.

snitchdns's People

Contributors

sadreck 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

snitchdns's Issues

Simple record creation?

Something else did actually come up :)

I was just wondering if there is a simple way of creating records in a single zone?

Eg. -> I create a home.lan zone, and in it have records for

  • snitch : 10.0.0.1
  • x : 10.0.0.2
  • y : 10.0.0.3
    etc...
    So when going to snitch.home.lan in my browser it would resolve to 10.0.0.1

Something similar to my Pi-Hole config?
image

I found the snitch config a bit confusing since I'm used to having a query and a response. Here I just see the response? Seems a bit overkill having to create zones for each query and then add A records individually
image

I might understand how basic DNS works, but apparently I'm not a guru, seeing all the options I could configure inside snitch.

Install failes with "python setup.py egg_info did not run successfully."

This is my first issue pardon me for missing anything. After installing python and the venv when installing from the requirements.txt I get "python setup.py egg_info did not run successfully." with "The license_file parameter is deprecated, use license_files instead." following in the output.

docker not started

Traceback (most recent call last):
File "/opt/snitchdns/venv/bin/flask", line 8, in
sys.exit(main())
File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask/cli.py", line 967, in main
cli.main(args=sys.argv[1:], prog_name="python -m flask" if as_module else None)
File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask/cli.py", line 586, in main
return super(FlaskGroup, self).main(*args, **kwargs)
File "/opt/snitchdns/venv/lib/python3.8/site-packages/click/core.py", line 782, in main
rv = self.invoke(ctx)
File "/opt/snitchdns/venv/lib/python3.8/site-packages/click/core.py", line 1254, in invoke
cmd_name, cmd, args = self.resolve_command(ctx, args)
File "/opt/snitchdns/venv/lib/python3.8/site-packages/click/core.py", line 1297, in resolve_command
cmd = self.get_command(ctx, cmd_name)
File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask/cli.py", line 542, in get_command
rv = info.load_app().cli.get_command(ctx, name)
File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask/cli.py", line 388, in load_app
app = locate_app(self, import_name, name)
File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask/cli.py", line 240, in locate_app
import(module_name)
File "/opt/snitchdns/app/init.py", line 12, in
db = SQLAlchemy()
File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask_sqlalchemy/init.py", line 758, in init
_include_sqlalchemy(self, query_class)
File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask_sqlalchemy/init.py", line 112, in _include_sqlalchemy
for key in module.all:
AttributeError: module 'sqlalchemy' has no attribute 'all'

Docker/Containerd build failed

I try to use containerd to build the docker image
I used ”nerdctl build -t snitchdns .“
Then it failed:
failed to ingest "blobs/sha256/b0cc371e7aa86d19fa79c5287223fb612aa86ef46d18296a8273df198719caa1": failed to read expected number of bytes: unexpected EOF

OS:Centos7.9.2009
CPU:AMD Ryzen 9 5900X

No such file or directory: 'which'

Uhhhhhh, not sure how this is happening, but it seems flask is not picking up the vanilla env variables.

The following shows up in the Web UI upon logging into the admin account:

This clearly isn't working. We need a break.

Error Description
[Errno 2] No such file or directory: 'which'
Traceback (most recent call last):
File "/opt/snitch/venv/lib/python3.9/site-packages/flask/app.py", line 1516, in full_dispatch_request
rv = self.dispatch_request()
File "/opt/snitch/venv/lib/python3.9/site-packages/flask/app.py", line 1502, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
File "/opt/snitch/app/controllers/auth.py", line 131, in login_process
system.run_updates()
File "/opt/snitch/app/lib/base/system.py", line 45, in run_updates
self.__update_git_hash_version()
File "/opt/snitch/app/lib/base/system.py", line 49, in __update_git_hash_version
git_binary = self.shell.execute(['which', 'git'])
File "/opt/snitch/app/lib/base/shell.py", line 16, in execute
return self.__execute(command, wait)
File "/opt/snitch/app/lib/base/shell.py", line 20, in __execute
return subprocess.run(command, stdout=subprocess.PIPE).stdout.decode().strip()
File "/usr/lib/python3.9/subprocess.py", line 505, in run
with Popen(*popenargs, **kwargs) as process:
File "/usr/lib/python3.9/subprocess.py", line 951, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "/usr/lib/python3.9/subprocess.py", line 1823, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'which'

systemctl status snitchdns:
Notice the /usr/bin/env: 'bash': No such file or directory near the end

snitchdns.service - SnitchDNS Gunicorn
     Loaded: loaded (/opt/snitch/data/config/service/snitchdns.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2022-02-25 14:05:13 CET; 6min ago
    Process: 2309667 ExecStartPost=/opt/snitch/venv/bin/python3 -m flask snitch_start (code=exited, status=0/SUCCESS)
   Main PID: 2309666 (gunicorn)
      Tasks: 4 (limit: 1100)
     Memory: 184.1M
        CPU: 3.046s
     CGroup: /system.slice/snitchdns.service
             ├─2309666 /opt/snitch/venv/bin/python3 /opt/snitch/venv/bin/gunicorn --workers 3 --bind 12.0.0.2:8080 -m 007 wsg>
             ├─2309668 /opt/snitch/venv/bin/python3 /opt/snitch/venv/bin/gunicorn --workers 3 --bind 12.0.0.2:8080 -m 007 wsg>
             ├─2309669 /opt/snitch/venv/bin/python3 /opt/snitch/venv/bin/gunicorn --workers 3 --bind 12.0.0.2:8080 -m 007 wsg>
             └─2309670 /opt/snitch/venv/bin/python3 /opt/snitch/venv/bin/gunicorn --workers 3 --bind 12.0.0.2:8080 -m 007 wsg>

Feb 25 14:05:06 ho-dns systemd[1]: Starting SnitchDNS Gunicorn...
Feb 25 14:05:06 ho-dns gunicorn[2309666]: [2022-02-25 14:05:06 +0100] [2309666] [INFO] Starting gunicorn 20.1.0
Feb 25 14:05:06 ho-dns gunicorn[2309666]: [2022-02-25 14:05:06 +0100] [2309666] [INFO] Listening at: http://12.0.0.2:8080 (23>
Feb 25 14:05:06 ho-dns gunicorn[2309666]: [2022-02-25 14:05:06 +0100] [2309666] [INFO] Using worker: sync
Feb 25 14:05:06 ho-dns gunicorn[2309668]: [2022-02-25 14:05:06 +0100] [2309668] [INFO] Booting worker with pid: 2309668
Feb 25 14:05:06 ho-dns gunicorn[2309669]: [2022-02-25 14:05:06 +0100] [2309669] [INFO] Booting worker with pid: 2309669
Feb 25 14:05:07 ho-dns gunicorn[2309670]: [2022-02-25 14:05:07 +0100] [2309670] [INFO] Booting worker with pid: 2309670
Feb 25 14:05:08 ho-dns python3[2309671]: /usr/bin/env: ‘bash’: No such file or directory
Feb 25 14:05:13 ho-dns python3[2309667]: SnitchDNS - Starting daemon...
Feb 25 14:05:13 ho-dns systemd[1]: Started SnitchDNS Gunicorn.

/etc/systemd/system/snitchdns.service:

[Unit]
Description=SnitchDNS Gunicorn
After=network.target
StartLimitBurst=5
StartLimitIntervalSec=30

[Service]
Restart=always
RestartSec=30
User=www-data
Group=www-data
WorkingDirectory=/opt/snitch
EnvironmentFile=/etc/environment
EnvironmentFile=/opt/snitch/data/config/env/snitch.conf
Environment="PATH=/opt/snitch/venv/bin:$PATH"
#ExecStart=/opt/snitch/venv/bin/gunicorn --workers 3 --bind 12.0.0.2:8080 --certfile=/opt/snitch/data/config/http/gunicorn.crt --keyfile=/opt/snitch/data/config/http/gunicorn.pem -m 007 wsgi:app
ExecStart=/opt/snitch/venv/bin/gunicorn --workers 3 --bind 12.0.0.2:8080 -m 007 wsgi:app
ExecStartPost=/opt/snitch/venv/bin/python3 -m flask snitch_start

[Install]
WantedBy=multi-user.target

Feature Request: Restart Daemon Button

So this is just a QOL thing, but it was a bit annoying when my entire infrastructure stopped working (since it depends on DNS) after I pressed the stop button :)

image

Would be better if we had a restart button instead.

Issue with Docker Setup

Hi when I try to run the Docker setup it throw an error that look like this

------
 > [11/22] RUN ./venv.sh flask db init &&     ./venv.sh flask db migrate &&     ./venv.sh flask db upgrade &&     ./venv.sh flask snitchdb &&     ./venv.sh flask crontab add:
#0 1.037 Traceback (most recent call last):
#0 1.037   File "/opt/snitchdns/venv/bin/flask", line 8, in <module>
#0 1.037     sys.exit(main())
#0 1.037   File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask/cli.py", line 967, in main
#0 1.037     cli.main(args=sys.argv[1:], prog_name="python -m flask" if as_module else None)
#0 1.037   File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask/cli.py", line 586, in main
#0 1.037     return super(FlaskGroup, self).main(*args, **kwargs)
#0 1.037   File "/opt/snitchdns/venv/lib/python3.8/site-packages/click/core.py", line 782, in main
#0 1.037     rv = self.invoke(ctx)
#0 1.037   File "/opt/snitchdns/venv/lib/python3.8/site-packages/click/core.py", line 1254, in invoke
#0 1.037     cmd_name, cmd, args = self.resolve_command(ctx, args)
#0 1.037   File "/opt/snitchdns/venv/lib/python3.8/site-packages/click/core.py", line 1297, in resolve_command
#0 1.037     cmd = self.get_command(ctx, cmd_name)
#0 1.037   File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask/cli.py", line 542, in get_command
#0 1.037     rv = info.load_app().cli.get_command(ctx, name)
#0 1.037   File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask/cli.py", line 388, in load_app
#0 1.037     app = locate_app(self, import_name, name)
#0 1.037   File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask/cli.py", line 240, in locate_app
#0 1.037     __import__(module_name)
#0 1.037   File "/opt/snitchdns/app/__init__.py", line 12, in <module>
#0 1.037     db = SQLAlchemy()
#0 1.037   File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask_sqlalchemy/__init__.py", line 758, in __init__
#0 1.037     _include_sqlalchemy(self, query_class)
#0 1.037   File "/opt/snitchdns/venv/lib/python3.8/site-packages/flask_sqlalchemy/__init__.py", line 112, in _include_sqlalchemy
#0 1.037     for key in module.__all__:
#0 1.037 AttributeError: module 'sqlalchemy' has no attribute '__all__'
------

And when I tried to upgrade the Flask-SQLAlchemy version to >= 3.0.2 it break like this

 => [10/22] RUN ln -s /opt/snitchdns/data/config/env/snitch.conf /opt/snitchdns/.env                                                                                                                                                 0.4s
 => ERROR [11/22] RUN ./venv.sh flask db init &&     ./venv.sh flask db migrate &&     ./venv.sh flask db upgrade &&     ./venv.sh flask snitchdb &&     ./venv.sh flask crontab add                                                 1.0s
------
 > [11/22] RUN ./venv.sh flask db init &&     ./venv.sh flask db migrate &&     ./venv.sh flask db upgrade &&     ./venv.sh flask snitchdb &&     ./venv.sh flask crontab add:
#0 0.913 Usage: flask [OPTIONS] COMMAND [ARGS]...
#0 0.913 Try 'flask --help' for help.

Is there any step that I'm missing? Thank you in advance

'DatabaseDNSResolver' object has no attribute 'lookupAllRecords'

INFO:twisted:Lookup failed
INFO:twisted:<Query xyz.xyz.xyz ALL_RECORDS IN> query from ('xxx.xxx.xxx.xxx', 52909)
Unhandled Error
Traceback (most recent call last):
  File "/opt/snitchdns/venv/lib/python3.8/site-packages/twisted/names/server.py", line 538, in messageReceived
    self.handleQuery(message, proto, address)
  File "/opt/snitchdns/app/lib/daemon/server/factory.py", line 50, in handleQuery
    return super().handleQuery(message, protocol, address)
  File "/opt/snitchdns/venv/lib/python3.8/site-packages/twisted/names/server.py", line 371, in handleQuery
    self.resolver.query(query)
  File "/opt/snitchdns/venv/lib/python3.8/site-packages/twisted/names/common.py", line 78, in query
    return defer.maybeDeferred(method, query.name.name, timeout)
--- <exception caught here> ---
  File "/opt/snitchdns/venv/lib/python3.8/site-packages/twisted/internet/defer.py", line 191, in maybeDeferred
    result = f(*args, **kwargs)
  File "/opt/snitchdns/venv/lib/python3.8/site-packages/twisted/names/resolve.py", line 88, in lookupAllRecords
    d = self.resolvers[0].lookupAllRecords(name, timeout)
builtins.AttributeError: 'DatabaseDNSResolver' object has no attribute 'lookupAllRecords'

CRITICAL:twisted:Unhandled Error
Traceback (most recent call last):
  File "/opt/snitchdns/venv/lib/python3.8/site-packages/twisted/names/server.py", line 538, in messageReceived
    self.handleQuery(message, proto, address)
  File "/opt/snitchdns/app/lib/daemon/server/factory.py", line 50, in handleQuery
    return super().handleQuery(message, protocol, address)
  File "/opt/snitchdns/venv/lib/python3.8/site-packages/twisted/names/server.py", line 371, in handleQuery
    self.resolver.query(query)
  File "/opt/snitchdns/venv/lib/python3.8/site-packages/twisted/names/common.py", line 78, in query
    return defer.maybeDeferred(method, query.name.name, timeout)
--- <exception caught here> ---
  File "/opt/snitchdns/venv/lib/python3.8/site-packages/twisted/internet/defer.py", line 191, in maybeDeferred
    result = f(*args, **kwargs)
  File "/opt/snitchdns/venv/lib/python3.8/site-packages/twisted/names/resolve.py", line 88, in lookupAllRecords
    d = self.resolvers[0].lookupAllRecords(name, timeout)
builtins.AttributeError: 'DatabaseDNSResolver' object has no attribute 'lookupAllRecords'

INFO:twisted:Replying with no answers
INFO:twisted:Processed query in 0.012 seconds

I've seen this with multiple lookup domains from multiple IP addresses, but each time it is the ALL_RECORDS query that causes the error.

However if I manually do the same query with dig it doesn't seem to trigger the error...

Creating TXT record through REST API fails with a 5005 Missing type property data

JSON object to add:
{ "class":"IN", "type":"TXT", "ttl":3600, "active":true, "data":{ "data":"text-string" }, "is_conditional":false, "conditional_count":0, "conditional_limit":0, "conditional_reset":false, "conditional_data":{ } }

Error:
{ "code": 5005, "details": [ "Missing type property data" ], "message": "Invalid type property fields", "success": false }

Docker container does not respond to SIGTERM signal

When calling docker stop snitch the container does not shutdown the services and exits. So it does not respond to SIGTERM signals. Docker will wait some time, by default 10 seconds, and then send SIGKILL to kill the process. Always waiting 10 seconds is kind of annoying. As a consequence your app won't shutdown gracefully at all in case that is important.

The issue is that you are calling bash entrypoint.sh to start 3 processes and bash ignores signals and does not forward them to processes. As a workaround I have to use docker run --init to let docker insert a small init process as PID 1 which then manages your bash entrypoint.sh process. That way I don't have to wait 10 seconds, but signals still won't be forwarded to your processes for graceful shutdown.

So at least add --init to your tutorial and/or make your PID 1 process more compliant.

DNS over HTTPS

Not sure if this is actually needed, but it'd be a good feature to add.

Implement module support

The idea would be to allow users to create a custom handler/module for processing DNS requests, in order to extend its functionality.

For example, if you want to add your own conditions on what to return that may not be currently supported by SnitchDNS itself.

No specific plans on how this will be implemented, but it would be a great extensibility feature.

Functional enhancement request: RADIUS authentication

I find the multi-user support a great feature. I would like to use this function with a Radius server. My LAN and WLAN runs with Unifi hardware and IEEE 802.1x. So I already use a Radius Server in my network. I would like to give my family members the possibility for *.firstname.surname.local.

Thanks a lot

Release egress scripts

Scripts are almost ready, run a few extra tests and release with instructions on how to use.

  • Bash Scripts
  • PowerShell Scripts
  • Documentation

Clearing the database?

Hey, so I just wanted to check in on an opinion.
This is how my DB looks after 2 weeks of use:

image

This made me think - performance/storage related - if maybe there should be an option for pruning the DB of entries older than N (days,months,years) in the web-ui, with some sort of checkbox even for automatic pruning.


Also another question about caching - Is it actually implemented since in the docs i notice:
https://github.com/ctxis/SnitchDNS#limitations

Caching has not been implemented, which means this isn't suitable for environments with hundreds of DNS requests per minute.

But in the web-ui there is an option for caching:
image

If it is actually implemented, I'm guessing 0 stands for infinite? Is then there a time limit for how long it keeps the cache or?

Conditional Responses: Time Restrictions

A feature to set time restrictions on responses, for example between 0900 - 1730 to only limit resolution during working hours.

This will probably require a face-lift for the whole conditional responses page, in order to develop it in such a way that it'd be easy to add extra conditions in the future.

It is possible to use hostname in A and AAAA record?

Hello

I think I know the answer, but I still want to ask in case I missed something.
Based on https://ns1.com/resources/dns-types-records-servers-and-queries I understand that the A record contain hostname and IP.

I guess I can make it work by making a new zone for each domain like www.example.com instead of using the zone example.com with an A record that will contain the hostname www and the ipv4. From my reading, it looks like it's not implemented in twisted, but maybe I understand something wrong.

Thanks for your help.

Cheers

https website forcibly redirects to http

I am using snitchdns deployed over docker. The DNS as such works fine. The difficulty is in accessing the ui.

Only the https server is working. http just returns not found response.

Morevoer whenever I submit a form in the ui, it redirects to the http site and goes to 404 not found.

I have to manually change the url each time from http to https.

If this bug is fixed, the system is really easy to setup and use.

Regex Matches

Allow to create domains that match a regular expression than to be explicitly defined.

For example, something like hello[0-9]{3}

An issue that needs to be considered is that a domain may match more than one regular expressions, so there should be documentation/warnings around this, or some sort of priority setting.

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.