Coder Social home page Coder Social logo

chisel's Introduction

chisel Snap Build Tests

Chisel

Chisel is a software tool for carving and cutting Debian packages!

It is built on the idea of package slices - minimal, complimentary and loosely coupled sets of files, based on the package’s metadata and content. Slices are basically subsets of the Debian packages, with their own content and set of dependencies to other internal and external slices.

pkg-slices


a-slice-of-ubuntu

This image depicts a simple case, where both packages A and B are deconstructed into multiple slices. At a package level, B depends on A, but in reality, there might be files in A that B doesn’t actually need (eg. A_slice3 isn’t needed for B to function properly). With this slice definition in place, Chisel is able to extract a highly-customized and specialized Slice of the Ubuntu distribution, which one could see as a block of stone from which we can carve and extract small and relevant parts we need to run our applications. It is ideal to support the creation of smaller but equally functional container images.

“The sculpture is already complete within the marble block, before I start my work. It is already there, I just have to chisel away the superfluous material.”

- Michelangelo

In the end, it’s like having a slice of Ubuntu - get just what you need. You can have your cake and eat it too!

Using Chisel

To install the latest version of Chisel, run the following command:

go install github.com/canonical/chisel/cmd/chisel@latest

Chisel is invoked using chisel <command>. To get more information:

  • To see a help summary, type chisel -h.
  • To see a short description of all commands, type chisel help --all.
  • To see details for one command, type chisel help <command> or chisel <command> -h.

Example command

Chisel relies on a database of slices that are indexed per Ubuntu release.

chisel cut --release ubuntu-22.04 --root myrootfs/ libgcc-s1_libs libssl3_libs

In this example, Chisel would look into the Ubuntu Jammy archives, fetch the provided packages and install only the desired slices into the myrootfs folder, according to the slice definitions available in the "ubuntu-22.04" chisel-releases branch.

Reference

Chisel releases

As mentioned above, Chisel relies on package slices. These slices need to be defined prior to the execution of the chisel command.

By default, Chisel will look into its central chisel-releases database, where package slices are defined and indexed by Ubuntu release. A release is identified by the branch name. For example:

chisel cut --release ubuntu-22.10 ...

will tell Chisel to look for a chisel.yaml file in the "ubuntu-22.10" branch of the chisel-releases repository. This file will in turn instruct Chisel to fetch the requested package slices, as defined in the same branch, from the corresponding Kinetic release in the Ubuntu archives.

Alternatively, one can also point Chisel to a custom and local Chisel release by specifying a path instead of a branch name. For example:

chisel cut --release release/ ...

Chisel release configuration

Each Chisel release must have one "chisel.yaml" file.

chisel.yaml:

format: <chiselReleaseFormat>

archives:
    ubuntu:
        # Ubuntu archive for Chisel to look into
        version: <ubuntuRelease>

        # categories/components of the Ubuntu archive to look into
        components: [<componentName>, ...]

        # pockets/suites of the Ubuntu archive to look into
        suites: [<pocket>, ...]

Example:

format: v1

archives:
    ubuntu:
        version: 22.04
        components: [main, universe]
        suites: [jammy, jammy-security, jammy-updates]
        public-keys: [ubuntu-archive-key-2018]

public-keys:
    # Ubuntu Archive Automatic Signing Key (2018) <[email protected]>
    # rsa4096/f6ecb3762474eda9d21b7022871920d1991bc93c 2018-09-17T15:01:46Z
    ubuntu-archive-key-2018:
        id: "871920D1991BC93C"
        armor: |
            -----BEGIN PGP PUBLIC KEY BLOCK-----

            mQINBFufwdoBEADv/Gxytx/LcSXYuM0MwKojbBye81s0G1nEx+lz6VAUpIUZnbkq
            dXBHC+dwrGS/CeeLuAjPRLU8AoxE/jjvZVp8xFGEWHYdklqXGZ/gJfP5d3fIUBtZ
            HZEJl8B8m9pMHf/AQQdsC+YzizSG5t5Mhnotw044LXtdEEkx2t6Jz0OGrh+5Ioxq
            X7pZiq6Cv19BohaUioKMdp7ES6RYfN7ol6HSLFlrMXtVfh/ijpN9j3ZhVGVeRC8k
            KHQsJ5PkIbmvxBiUh7SJmfZUx0IQhNMaDHXfdZAGNtnhzzNReb1FqNLSVkrS/Pns
            AQzMhG1BDm2VOSF64jebKXffFqM5LXRQTeqTLsjUbbrqR6s/GCO8UF7jfUj6I7ta
            LygmsHO/JD4jpKRC0gbpUBfaiJyLvuepx3kWoqL3sN0LhlMI80+fA7GTvoOx4tpq
            VlzlE6TajYu+jfW3QpOFS5ewEMdL26hzxsZg/geZvTbArcP+OsJKRmhv4kNo6Ayd
            yHQ/3ZV/f3X9mT3/SPLbJaumkgp3Yzd6t5PeBu+ZQk/mN5WNNuaihNEV7llb1Zhv
            Y0Fxu9BVd/BNl0rzuxp3rIinB2TX2SCg7wE5xXkwXuQ/2eTDE0v0HlGntkuZjGow
            DZkxHZQSxZVOzdZCRVaX/WEFLpKa2AQpw5RJrQ4oZ/OfifXyJzP27o03wQARAQAB
            tEJVYnVudHUgQXJjaGl2ZSBBdXRvbWF0aWMgU2lnbmluZyBLZXkgKDIwMTgpIDxm
            dHBtYXN0ZXJAdWJ1bnR1LmNvbT6JAjgEEwEKACIFAlufwdoCGwMGCwkIBwMCBhUI
            AgkKCwQWAgMBAh4BAheAAAoJEIcZINGZG8k8LHMQAKS2cnxz/5WaoCOWArf5g6UH
            beOCgc5DBm0hCuFDZWWv427aGei3CPuLw0DGLCXZdyc5dqE8mvjMlOmmAKKlj1uG
            g3TYCbQWjWPeMnBPZbkFgkZoXJ7/6CB7bWRht1sHzpt1LTZ+SYDwOwJ68QRp7DRa
            Zl9Y6QiUbeuhq2DUcTofVbBxbhrckN4ZteLvm+/nG9m/ciopc66LwRdkxqfJ32Cy
            q+1TS5VaIJDG7DWziG+Kbu6qCDM4QNlg3LH7p14CrRxAbc4lvohRgsV4eQqsIcdF
            kuVY5HPPj2K8TqpY6STe8Gh0aprG1RV8ZKay3KSMpnyV1fAKn4fM9byiLzQAovC0
            LZ9MMMsrAS/45AvC3IEKSShjLFn1X1dRCiO6/7jmZEoZtAp53hkf8SMBsi78hVNr
            BumZwfIdBA1v22+LY4xQK8q4XCoRcA9G+pvzU9YVW7cRnDZZGl0uwOw7z9PkQBF5
            KFKjWDz4fCk+K6+YtGpovGKekGBb8I7EA6UpvPgqA/QdI0t1IBP0N06RQcs1fUaA
            QEtz6DGy5zkRhR4pGSZn+dFET7PdAjEK84y7BdY4t+U1jcSIvBj0F2B7LwRL7xGp
            SpIKi/ekAXLs117bvFHaCvmUYN7JVp1GMmVFxhIdx6CFm3fxG8QjNb5tere/YqK+
            uOgcXny1UlwtCUzlrSaP
            =9AdM
            -----END PGP PUBLIC KEY BLOCK-----

Slice definitions

There can be only one slice definitions file for each Ubuntu package, per Chisel release. All of the slice definitions files must be placed under a "slices" folder, and follow the same structure. For example:

slices/B.yaml:

# (req) Name of the package.
# The slice definition file should be named accordingly (eg. "openssl.yaml")

package: B

# (req) List of slices
slices:

    # (req) Name of the slice
    slice2:

        # (opt) Optional list of slices that this slice depends on
        essential:
          - A_slice1

        # (req) The list of files, from the package, that this slice will install
        contents:
            /path/to/content:
            /path/to/another/multiple*/content/**:
            /path/to/moved/content: {copy: /bin/original}
            /path/to/link: {symlink: /bin/mybin}
            /path/to/new/dir: {make: true}
            /path/to/file/with/text: {text: "Some text"}
            /path/to/mutable/file/with/default/text: {text: FIXME, mutable: true}
            /path/to/temporary/content: {until: mutate}

        # (opt) Mutation scripts, to allow for the reproduction of maintainer scripts,
        # based on Starlark (https://github.com/google/starlark-go)
        mutate: |
            foo = content.read("/path/to/temporary/content")
            content.write("/path/to/mutable/file/with/default/text", foo)

Example:

package: mypkg

slices:
    bins:
        essential:
            - mypkg_config

        contents:
            /bin/mybin:
            /bin/moved:  {copy: /bin/original}
            /bin/linked: {symlink: /bin/mybin}

    config:
        contents:
            /etc/mypkg.conf: {text: "The configuration."}
            /etc/mypkg.d/:   {make: true}

To find more examples of real slice definitions files (and contribute your own), please go to https://github.com/canonical/chisel-releases.

Path kinds

As depicted in the example above, the paths listed under a slice's contents can have additional information for identifying the kind of content to expect:

  • make: a true or false boolean value to specify whether the path must be created or not. Example: /etc/mypkg.d/: {make: true} instructs Chisel to create the directory "/etc/mypkg.d/" (with parent directories). NOTE: the provided path must end with "/" for make to be valid.
  • mode: a 32-bit unsigned integer representing the path mode. Example: /etc/dir/sub/: {make: true, mode: 01777} instructs Chisel to create the directory "/etc/dir/sub/" with mode "01777".
  • copy: a string referring to the original path of the content being copied. Example: /bin/moved: {copy: /bin/original} instructs Chisel to copy the package's "/bin/original" file onto "/bin/moved".
  • text: a sequence of characters to be written to the provided file path. Example: /tmp/file1: {text: data1} will instruct Chisel to write "data1" into the file "/tmp/file1".
  • symlink: a string referring to the original path (source) of the content being linked. Example: /bin/linked: {symlink: /bin/mybin} will instruct Chisel to create the symlink "/bin/linked", which points to an existing file "/bin/mybin".
  • mutable: a true or false boolean value to specify whether the content is mutable, i.e. it can be changed after being extracted from the deb. Example: /tmp/file1: {text: data1, mutable: true} instructs Chisel to populate "/tmp/file1" with "data1", while also letting Chisel know that this file's content can be mutated via a mutation script.
  • until: accepts a mutate value to say that the specified content shall be removed by Chisel after the mutation scripts are executed. Example: /tmp/file1: {text: data1, until: mutate} instructs Chisel to populate the file "/tmp/file1" with "data1" at installation time, but to then remove it right after the slice's mutation scripts are executed. NOTE: while this option can be combined with globs (eg. /tmp/file*: {until: mutate}), it cannot be used to remove non-empty directories.
  • arch: accepts a list of known architectures for identifying contents which are only available for certain architectures. Example: /usr/bin/hello: {arch: amd64} will instruct Chisel to extract and install the "/usr/bin/hello" file only when chiselling an amd64 filesystem.

TODO

  • Preserve ownerships when possible
  • GPG signature checking for archives
  • Use a fake server for the archive tests
  • Functional tests

FAQ

May I use arbitrary package names?

No, package names must reflect the package names in the archive, so that there's a single namespace to remember and respect.

I've tried to use a different Ubuntu version and it failed?

The mapping is manual for now. Let us know and we'll fix it.

Can I use multiple repositories in a Chisel release?

Not at the moment, but maybe eventually.

Can I use non-Ubuntu repositories?

Not at the moment, but eventually.

Can multiple slices refer to the same path?

Yes, but see below.

Can multiple slices output the same path?

Yes, as long as either both slices are part of the same package, or the path is not extracted from a package at all (not copied) and the explicit inline definitions match exactly.

Is file ownership preserved?

Not right now, but it will be supported.

chisel's People

Contributors

cjdcordeiro avatar iainlane avatar letfunny avatar niemeyer avatar rebornplusplus avatar shyim avatar thesignpainter98 avatar woky 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

chisel's Issues

Overlays of slices config

I would like to have overlays of the slices config. Right now I have a fork of chisel releases and have my additional packages. This is annoying. I would love to have the option to have overlays like:

chisel cut --release github:canonical/chisel-releases#ubuntu-24.04,github:shyim/xxxx:#branch,file:.

and in my Repository or local path, I would only define the newly added slices; the order of overlays specifies the inheritance

content.list() fails with "cannot read file not selected" in every case

I've tried to use content.list() in ca-certificates_data slice. Below are several variants I've tried, none of which worked. (For text: todo please see #6).

$ cat slices/ca-certificates.yaml
package: ca-certificates
slices:
  data:
    contents:
      /etc/ssl/certs/ca-certificates.crt: { text: todo, mutable: true }
      /usr/share/ca-certificates/mozilla/*.crt:
    mutate: |
      certs = [content.read(path) for path in content.list("/usr/share/ca-certificates/mozilla/")]
      content.write("/etc/ssl/certs/ca-certificates.crt", "".join(certs))
$ chisel cut --release $PWD --root ~/tmp/chisel-out ca-certificates_data
...
2022/06/10 09:14:23 Extracting files from package "ca-certificates"...
error: slice ca-certificates_data: cannot read file not selected: /usr/share/ca-certificates/mozilla

More variants of contents that produce the same error:

    contents:
      /etc/ssl/certs/ca-certificates.crt: { text: todo, mutable: true }
      /usr/share/ca-certificates/mozilla/:
      /usr/share/ca-certificates/mozilla/*.crt:
    contents:
      /etc/ssl/certs/ca-certificates.crt: { text: todo, mutable: true }
      /usr/share/ca-certificates/mozilla/:
      /usr/share/ca-certificates/mozilla/ISRG_Root_X1.crt:

Chisel image not available in docker hub

~ docker pull chisel:22.04
Error response from daemon: pull access denied for chisel, repository does not exist or may require 'docker login': denied: requested access to the resource is denied

Support package names shorter than 3 characters

Chisel does not currently support package names shorter than 3 characters. This is controlled by a few regular expressions specified in the setup package.

var fnameExp = regexp.MustCompile(`^([a-z0-9](?:-?[.a-z0-9+]){2,})\.yaml$`)
var snameExp = regexp.MustCompile(`^([a-z](?:-?[a-z0-9]){2,})$`)
var knameExp = regexp.MustCompile(`^([a-z0-9](?:-?[.a-z0-9+]){2,})_([a-z](?:-?[a-z0-9]){2,})$`)

However, this means that packages like jq are not currently supported. Per the debian policy, the minimum length of a package's name can be 2.

Package names (both source and binary, see Package) must consist only of lower case letters (a-z), digits (0-9), plus (+) and minus (-) signs, and periods (.). They must be at least two characters long and must start with an alphanumeric character.

Related: canonical/chisel-releases#116

Spelling mistakes

./internal/setup/setup.go: packages typo:

// the real information coming from pacakges is still unknown, so referenced
// paths could potentially be missing, for example.
type Selection struct {
Release *Release
Slices []*Slice
}

Chisel dies on symlink when run for second time

Chisel version: 2b260842c9c

Currently chisel cannot be run repeatedly on already populated output directory when there are symlinks. IOW chisel run is not idempotent.

Example slice (libc6_libs dependency omitted for brevity):

$ cat slices/libkeyutils1.yaml 
package: libkeyutils1

slices:
  libs:
    contents:
      /lib/x86_64-linux-gnu/libkeyutils.so.*:

First run is OK:

$ mkdir output
$ chisel cut --release . --root output/ libkeyutils1_libs
...
2022/06/03 12:16:25 Extracting files from package "libkeyutils1"...
$ tree --noreport -p output/
[drwxrwxr-x]  output/
└── [drwxr-xr-x]  lib
    └── [drwxr-xr-x]  x86_64-linux-gnu
        ├── [lrwxrwxrwx]  libkeyutils.so.1 -> libkeyutils.so.1.9
        └── [-rw-r--r--]  libkeyutils.so.1.9

Second run fails (no changes in output/):

$ chisel cut --release . --root output/ libkeyutils1_libs
...
2022/06/03 12:17:03 Extracting files from package "libkeyutils1"...
error: cannot extract from package "libkeyutils1": symlink libkeyutils.so.1.9 output/lib/x86_64-linux-gnu/libkeyutils.so.1: file exists

Configuration file instead of flags

I would like to have a simple configuration file instead of passing a lot of parameters. I could imagine something like:

source: ubuntu-22.04
packages:
  - dash-bins
  - ....

In that way, I would get rid of having a shell script with all the flags

Feat: let chisel fetch DEBs for the same architecture as the underlying execution environment

At the moment, only amd64 seems to be supported.

Steps to reproduce

(if emulation is needed, simply run the following with docker buildx for ease of use. Example docker buildx build --platform=linux/arm64 . --progress plain)

Run on any arch different from amd64:

chisel cut --release ubuntu-22.04 --root /tmp openssl_config

Output:

#8 0.142 2022/07/01 15:45:42 Consulting release repository...
#8 0.413 2022/07/01 15:45:43 Fetching current ubuntu-22.04 release...
#8 0.416 2022/07/01 15:45:43 Processing ubuntu-22.04 release...
#8 0.431 2022/07/01 15:45:43 Selecting slices...
#8 0.431 2022/07/01 15:45:43 Fetching ubuntu 22.04 archive details...
#8 1.171 2022/07/01 15:45:44 Release date: Thu, 21 Apr 2022 17:16:08 UTC
#8 1.171 2022/07/01 15:45:44 Fetching ubuntu 22.04 main component...
#8 1.628 2022/07/01 15:45:44 Fetching ubuntu 22.04 universe component...
#8 4.639 2022/07/01 15:45:47 Fetching pool/main/o/openssl/openssl_3.0.2-0ubuntu1_amd64.deb...
#8 5.210 2022/07/01 15:45:48 Extracting files from package "openssl"...

Notice the line

#8 4.639 2022/07/01 15:45:47 Fetching pool/main/o/openssl/openssl_3.0.2-0ubuntu1_amd64.deb...

Proposed solution

Before defining the archives, infer the underlying architecture instead of defaulting to amd64.

NOTE: this was already planned at

Arch: "amd64", // TODO Option for this, implied from running system

Questions

  1. do we need to define Slice Definition Files / package / architecture? Or can we just have one Slice Definition File per package (as we do right now, for each release), and somehow have a new directive to let us identify contents which are architecture specific?

NodeJS slice

Is there a way to add nodejs package sources and create slice for NodejS to be able to create a small docker image for nodeJs application ?

Paths from slice dependency are not preserved (symlink vs directory)

[NOTE: I'm telling chisel to create /lib symlink because base-files contains it not as a symlink but as a directory and dotnet package needs that to be a symlink because the dotnet executable links to /lib/x86_64-linux-gnu/libstdc++.so.6 but libstdc++6 package installs it in /usr/lib/x86_64-linux-gnu/libstdc++.so.6. On Ubuntu usrmerge package does the job in postinst but we don't have that so that's why I'm creating the symlink manually. (To make dotnet work.)]

When a slice lists a path entry as a symlink to directory and some other slice depends on it and installs its files into symlink, the symlink is not preserved but instead a directory is created at that path. Example:

Configuration:

$ tail release/slices/*
==> release/slices/base-files.yaml <==
package: base-files
slices:
  lib:
    contents:
      /lib: { symlink: /usr/lib }
      /usr/lib/:

==> release/slices/libc6.yaml <==
package: libc6
slices:
  libs:
    essential:
      - base-files_lib
    contents:
      /lib/x86_64-linux-gnu/libc.so.6:

In this snippet you can see that /lib is not a symlink even though libc6_libs depends on base-files_lib which should create it.

$ rm -rf output && mkdir output
$ chisel cut --release release/ --root output/ base-files_lib libc6_libs
...
2022/06/02 10:39:14 Extracting files from package "base-files"...
2022/06/02 10:39:14 Extracting files from package "libc6"...
$ tree --noreport output/
output/
├── lib
│   └── x86_64-linux-gnu
│       └── libc.so.6
└── usr
    └── lib

In this snippet you can see that /lib is created as symlink when no other slice installing files at that path is being installed. But afterwards it fails to install because of the symlink:

$ rm -rf output && mkdir output
$ chisel cut --release release/ --root output/ base-files_lib >/dev/null
...
2022/06/02 10:38:46 Extracting files from package "base-files"...
$ tree --noreport output/
output/
├── lib -> /usr/lib
└── usr
    └── lib
$ chisel cut --release release/ --root output/ libc6_libs
...
2022/06/02 10:38:58 Extracting files from package "base-files"...
2022/06/02 10:38:58 Extracting files from package "libc6"...
error: cannot extract files from package "libc6": open output/lib/x86_64-linux-gnu/libc.so.6: permission denied

Testing story of slices

I have been looking at this project and it seems very interesting, I won't enumerate but see many things to like compared to existing distroless techniques.

One thing I'm worried about is the guarantees towards compatibility. I am looking at python specifically, and while it's not there yet am glad to see what's probably upcoming support

canonical/chisel-releases#82

Looking at the PR I'm concerned though about the testing or robustness of the chisel. It is literally listing out individual .py files from the apks. Actually I found a similar PR where many comments were of the form "this file is missing".

I think two concrete questions that came to mind

  • Is there an absolute guarantee that files aren't added to an apk on patch releases? FWIU, chisels are targeted at an Ubuntu release version and will operate on the latest patch version of a package which can change, and if a file is added, it seems like it would be left out

  • Is there any plan for integration testing against real code, in this case Python? On the linked PR, there are no CI checks running from what I can tell. How is it ensured that a slice is compatible with the ecosystem it is targeting? Given robust compatibility tests, the previous point would be less of a big deal since failures due to new files would be detected

Knowing this story would really help deciding on using chisel. For context I am currently using Google distroless and don't like that updates are slow even in the face of CVEs. But I would need to stick with it if there is risk of mysterious build failures even when targeting a specific Ubuntu release line.

[RFC] Record installed slices in `--root` and install only what's missing

Currently it's not efficient to use chisel for an image that's based on another image that was also built using chisel. This is related to the current lack of any metadata/database of installed slices in the final images.

There's a desire to have 2 kind of images for some programming language environments (for example .NET or Go):

  • runtime-deps image: This image contains required dependencies to run self-contained applications compiled from an language SDK. Examples are self-contained application bundles in .NET or static binaries in Golang. These final products have no dependencies on the language runtime as they bundle it. But they still require libraries on which the language runtime depends.
  • runtime image: This image is super-set of runtime-deps containing language runtime in addition to its dependencies.

The way we currently use chisel is something a little bit more complex than this illustrative example (that also pretends chisel is in PATH):

FROM ubuntu:22.04 as builder
RUN chisel cut --release ubuntu-22.04 --root /rootfs slice1_libs slice2_libs

FROM scratch
COPY --from=builder /rootfs /

If we apply this for the 2 images described above, we would end up with something like the following 2 Dokcerfiles.

For runtime-deps image:

FROM ubuntu:22.04 as builder
RUN chisel cut --release ubuntu-22.04 --root /rootfs libc6_libs libgcc-s1_libs libstdc++6_libs

FROM scratch
COPY --from=builder /rootfs /

For runtime image (here .NET is used as an example):

FROM ubuntu:22.04 as builder
RUN chisel cut --release ubuntu-22.04 --root /rootfs-dotnet dotnet-runtime-6.0_libs

FROM runtime-deps
COPY --from=builder /rootfs-dotnet /

Even though we base our final runtime image on runtime-deps, we effectively overwrite everything that was installed in runtime-deps. Chisel cannot know that dependencies of dotnet-runtime-6.0 have already been installed so whole dependency tree is populated in /rootfs-dotnet.

In this simple example one could suggest to not base runtime image on runtime-deps image but instead build it from scratch. But more complex runtime-deps image could have something more in addition to root filesystem created by chisel, e.g.:

FROM ubuntu:22.04 as builder
RUN chisel cut --release ubuntu-22.04 --root /rootfs libc6_libs libgcc-s1_libs libstdc++6_libs \
  && echo app:x:999:999:app:/nonexistent:/nonexistent >/etc/passwd \
  && echo app:x:999: >/etc/group

FROM scratch
COPY --from=builder /rootfs /
ENV DOTNET_VERSION=6.0

Here it makes sense to base runtime image on runtime-deps image (above) because of the additional files created outside of chisel and ENV directives. (There could be also HEALTHCHECK, PORTS and other directives that we would like to inherit in runtime image.)

It'd be nice if chisel had an ability to record what has already been installed, and install only missing dependencies that were not recorded. It's a bit complicated by the fact that we execute chisel in stage container that doesn't have access to the image on top of which we want to copy new slices. But that can be solved by copying the suggested chisel metadata/database into the stage container, e.g. like this:

FROM ubuntu:22.04 as builder
COPY --from=dotnet-runtime-deps /var/lib/chisel.db /rootfs-dotnet/var/lib/chisel.db
RUN chisel cut --release ubuntu-22.04 --root /rootfs-dotnet dotnet-runtime-6.0_libs

FROM runtime-deps
COPY --from=builder /rootfs-dotnet /

Now chisel could read state of /rootfs-dotnet from /rootfs-dotnet/var/lib/chisel.db (or wherever we decide to store the metadata).

One major problem with this is that it'll probably break the assumption that scripts run in a closed world and will lead to non-determinism. Currently each chisel run creates new world from scratch and so scripts runs can be scheduled to produce deterministic output. With such split installation it would no longer hold.

allow multiple slices with the same content paths to co-exist within the same Chisel release

Diffs between dotnet-host-7.0 and dotnet-host SDFs

package: dotnet-host-7.0                                      | package: dotnet-host
slices:                                                         slices:
  bins:                                                           bins:
    essential:                                                      essential:
      - libc6_libs                                                    - libc6_libs
      - libgcc-s1_libs                                                - libgcc-s1_libs
      - libstdc++6_libs                                               - libstdc++6_libs
    contents:                                                       contents:
      /usr/lib/dotnet/dotnet:                                         /usr/lib/dotnet/dotnet:

Problem

By design, Chisel is currently looking at the entire Chisel release to catch any conflicts [1]. The reason why it was designed this way was to cope with people doing /usr/lib/* in their slices, thus avoiding the produced outputs from growing unbounded as fat packages change.

In practice, this means that even if we’re just installing .NET7, Chisel will still complain about conflicts with .NET6, even though it is not targeted. And that’s because dotnet-host-7.0 and dotnet-host both have the same content path.

Reproduce

Copy the two SDFs from above, into an existing chisel-release (tested with kinetic), as slices/dotnet-host-7.0.yaml and slices/dotnet-host.yaml, respectively.

Then run

$ chisel cut --release ./ --root rootfs base-files_base

And you’ll get

2023/02/03 12:36:33 Processing ./ release...
error: slices dotnet-host_bins and dotnet-host-7.0_bins conflict on /usr/lib/dotnet/dotnet

Chisel version: https://github.com/canonical/chisel/tree/bd27f8700cd7d2a6b4e0df6b10c3761c83a70485

Refs

[1] https://github.com/canonical/chisel/blob/main/internal/setup/setup.go#L154

context deadline exceeded error when running chisel cut

I've been seeing this error several times while building a Dockerfile which is running "chisel cut":

#13 [build 3/8] RUN mkdir /rootfs     && chisel cut --release "ubuntu-22.04" --root /rootfs         libicu70_libs
#13 sha256:1921268898730a52b3ffb9270b7fabb790a0c0182faae5fea318c7b6ed1208a9
#13 0.668 2022/07/21 16:28:54 Consulting release repository...
#13 1.138 2022/07/21 16:28:54 Fetching current ubuntu-22.04 release...
#13 1.140 2022/07/21 16:28:54 Processing ubuntu-22.04 release...
#13 1.153 2022/07/21 16:28:54 Selecting slices...
#13 1.153 2022/07/21 16:28:54 Fetching ubuntu 22.04 archive details...
#13 2.195 2022/07/21 16:28:55 Release date: Thu, 21 Apr 2022 17:16:08 UTC
#13 2.196 2022/07/21 16:28:55 Fetching ubuntu 22.04 main component...
#13 5.874 2022/07/21 16:28:59 Fetching ubuntu 22.04 universe component...
#13 35.89 error: cannot fetch from archive: context deadline exceeded (Client.Timeout or context cancellation while reading body)
#13 ERROR: executor failed running [/bin/sh -c mkdir /rootfs     && chisel cut --release "ubuntu-22.04" --root /rootfs         libicu70_libs]: exit code: 1

Simply re-running the build will cause it to succeed typically.

start versioning Chisel

At the moment, Chisel is not being released nor packaged for distribution. This means users need to build Chisel from source, always pointing to a specific Git commit (typically the head of the main branch).

We've had cases already, where the introduction of new Chisel features led to disruptions in the process of building container images which were cloning the main branch of Chisel on every build.

For example, older scripts, making use of git clone https://github.com/canonical/chisel && cd chisel && go build ./cmd/chisel, stopped working at eeba444, since go generate internal/deb/version.go needs to run before the build.

This is normal, and more equally disruptive changes could be expected as Chisel grows.

Instead of forcing Chisel users to pinpoint specific commits, it would be nice to have immutable pointers (tags, releases, or even container images) to specific versions of Chisel.

Question: should copyright files be copied as is?

When slicing a package, we'll most likely also want to make sure the corresponding copyright information is kept.

Our options are:

  1. simply install the DEB's copyright file as-is, or
  2. take only the relevant files paragraphs (corresponding to the installed files) of the DEB's copyright file. (update: it is not clear to me whether files paragraphs always refer to source files, or whether they can also refer to subset of license-compliant files which are being shipped within the DEB)

Number 2. seems more useful for performing post-build source pedigree analysis, as we'll only get what we actually have. On the other hand, by modifying the copyright file, we're not staying true to the DEB...

Question originated by: ubuntu-rocks/dotnet#28

Improve README

I came across chisel today. It looks like an exciting tool, but unfortunately the README doesn't make it very clear what its purpose is.

Could you please expand the README? Some suggestions:

  • Start with an overview of what the tool is
  • Give some examples of usage with explanations of what the commands do
  • Define any new terms you've invented (e.g. I see mention of slice but it's not very clear what that term means here)
  • Compare to similar projects in the space, noting when you might use the other project or when you might use chisel

How do I get required ubuntu commands while building distroless ubuntu image using chisel

I created docker image for my app using official ubuntu:20.04 docker image as base image. It consumed 300 MBs. I started exploring if I can reduce the image size further I came across chiesel by canonical. Following are some tutorial / links regarding chiesel. 1, 2, 3, 4. 5, 6 So, I started preparing distroless ubuntu 20.04 image for my app using chisel.

I tried following dockerfile:

ARG UBUNTU_RELEASE=20.04 CHISEL_VERSION=v0.9.0

FROM ubuntu:$UBUNTU_RELEASE AS chisel_ubuntu_builder

ARG TARGETARCH UBUNTU_RELEASE CHISEL_VERSION

RUN apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates git \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

ADD "https://github.com/canonical/chisel/releases/download/${CHISEL_VERSION}/chisel_${CHISEL_VERSION}_linux_${TARGETARCH}.tar.gz" chisel.tar.gz
RUN tar -xvf chisel.tar.gz -C /usr/bin/

RUN mkdir /rootfs

RUN chisel cut --release "${UBUNTU_RELEASE}" --root /rootfs base-files_base

FROM scratch
COPY --from=chisel_ubuntu_builder /rootfs /

I was able to build above image. But it contained empty file system. I realised that I need at least bash. But then I found that there is no chisel slice for bash for ubuntu 20.04 as can be cheched here. There is one for ubuntu 22.04 as can be checked here.

So I tried source bash from other ubuntu library as follows:

ARG UBUNTU_RELEASE=20.04 CHISEL_VERSION=v0.9.0

FROM ubuntu:$UBUNTU_RELEASE AS chisel_ubuntu_builder
# ... same as earlier dockerfile ...

FROM scratch
COPY --from=chisel_ubuntu_builder /rootfs /
# copy bash from bash_source
COPY --from=chisel_ubuntu_builder /rootfs /
COPY --from=chisel_ubuntu_builder /bin/bash /bin/bash
COPY --from=chisel_ubuntu_builder /bin/apt /bin/apt
COPY --from=chisel_ubuntu_builder /bin/apt-get /bin/apt-get
COPY --from=chisel_ubuntu_builder /usr/bin/bash /usr/bin/bash
COPY --from=chisel_ubuntu_builder /usr/bin/apt /usr/bin/apt
COPY --from=chisel_ubuntu_builder /usr/bin/apt-get /usr/bin/apt-get

I was able to build this image. But, I got following error while building my app image using above image as base image:

#5 [baseenv 2/4] RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then     add-apt-repository -y "deb  xenial-security main"; fi
#0 0.031 /bin/bash: if [ "$TARGETPLATFORM" = "linux/amd64" ]; then     add-apt-repository -y "deb  xenial-security main"; fi: No such file or directory
#5 ERROR: process "/bin/bash if [ \"$TARGETPLATFORM\" = \"linux/amd64\" ]; then     add-apt-repository -y \"deb  xenial-security main\"; fi" did not complete successfully: exit code: 127http://security.ubuntu.com/ubuntuhttp://security.ubuntu.com/ubuntuhttp://security.ubuntu.com/ubuntu

I guess, it now contains bash but now does not contain add-apt-repository. Do I need to copy this library too to my base image? I then quickly went through my app dockerfile based on ubuntu:20.04 image. It contained various other commands like apt, apt-get, add-apt-repository, chmod, cd. Do I have to copy them individually from other base image the way I am copying bash from ? Also, for my app, I am cloning various third party libraries and then running their files. Those files themselves might require other ubuntu commands.

Q1. Do I have to copy all such required commands from other base image?

This will be tedious since I will come to know what all things I required as and when image build fails. Then add COPY command to dockerfile and rebuild the image. So I want to know the recommeded procedure to build the distroless image for my app.

Q2. Also, is it fine to install libraries using apt-get install while building distroless image or it basically makes it not-so-distroless? If I should not be using apt-get to build distroless image, then how should I install all necessary dependency packages?

PS: The second image does not contain the bash also.
I get following error while running it:

$ docker run -it myimage /bin/bash
exec /bin/bash: no such file or directory

Same for /usr/bin/bash:

$ docker run -it myimage /usr/bin/bash
exec /bin/bash: no such file or directory

add support for arch-specific dependencies

Atm, we support slice contents that are application specific, however, there's no way to specify an essential which is only required for a specific architecture.

Example: libnode108 only needs libatomic1 for riscv64.

Permissions from deb archive are not preserved with non-zero umask

The permissions of /tmp directory in base-files packages are 01777:

$ apt-get download base-files
$ ar p base-files_12ubuntu4.1_amd64.deb data.tar.zst | tar --zstd -tvf - ./tmp
drwxrwxrwt root/root         0 2022-04-22 16:34 ./tmp/

However, chisel doesn't preserve it. Specifically, with default umask in Ubuntu, it removes write bit from others:

$ umask
0002
$ chisel cut --release $PWD --root $PWD/output  base-files_base
...
2022/06/17 12:41:53 Fetching pool/main/b/base-files/base-files_12ubuntu4_amd64.deb...
2022/06/17 12:41:53 Extracting files from package "base-files"...
$ ls -ld output/tmp
drwxrwxr-t 2 woky woky 4096 čen 17 12:41 output/tmp

As suggested in canonical/chisel-releases#3 (comment) this is probably an issue with umask. The following works:

$ umask 0
$ chisel cut --release $PWD --root $PWD/output  base-files_base
...
2022/06/17 12:45:30 Fetching pool/main/b/base-files/base-files_12ubuntu4_amd64.deb...
2022/06/17 12:45:30 Extracting files from package "base-files"...
$ ls -ld output/tmp
drwxrwxrwt 2 woky woky 4096 čen 17 12:45 output/tmp

Master thesis on container security

Hi everyone,

I am currently starting to write my master thesis on evaluating the security of software containers through reduction of potentially vulnerable components. Of course chisel should be part of it 😊

I am working as part of a product security team in a large enterprise and our scanners mostly find a lot. That's why we went deep into distroless and currently recommending it in order to reduce findings. In literature I read a lot that this will reduce the attack surface. Nevertheless in the evening I am wrapping my head around the question if this really leads to more secure images. Lots of scanner findings are not exploitable in the context of certain apps anyway, I suppose.

Currently I am doing some research on related work, and I found 2 very interesting papers (2022)

The first one is a comprehensive comparison of distroless vulnerability findings vs. other base images. I guess in my thesis I could extend this to also include chiseled images...
Second one is a Study of Exploitability & Impact of Base-Image Vulnerabilities"

The goal should be e.g. to make better statements on accepting risks(scanner findings)

Soon I will dive as deep as I can into the codebase here :)

Are there already any kind of "pre built" runtime images? Like java, node.js, python etc? I saw smt about .NET..

And is there any communications channel like slack/google group etc?

Appreciate any input 😊 and sorry for the spam here, could not find another discussion group...

Kind regards
Michael

Generate SBOM?

Are there any plans to have chisel generate an SBOM (either SPDX or CycloneDX format) from all the package slices it installs into the specified root directory?

RFE: Warn on unmatched paths

When some paths in contents do not match any files in the package, chisel silently ignores them. In the example below I slice /lib from base-files which in the package is actually a directory (/lib/, with slash at the end, would work) but I expected it to be a symlink. I should be told that the path didn't match anything.

$ cat release/slices/base-files.yaml 
package: base-files
slices:
  lib:
    contents:
      /lib:
      /nonexistent:
$ rm -rf output/ && mkdir output
$ chisel cut --release release/ --root output/ base-files_lib 
...
2022/06/02 10:54:23 Extracting files from package "base-files"...
$ find output/
output/

content.read() fails on "invalid content path"

With following slice in slices/base-passwd.yaml:

package: base-passwd
slices:
  base:
    contents:
      /usr/share/base-passwd/group.master:
    mutate: |
      data = content.read("/usr/share/base-passwd/group.master")

The chisel fails with following:

$ ~/devel/chisel-releases
$ chisel cut --release $PWD --root output  base-passwd_base
...
2022/06/14 10:52:43 Fetching pool/main/b/base-passwd/base-passwd_3.5.52build1_amd64.deb...
2022/06/14 10:52:43 Extracting files from package "base-passwd"...
error: slice base-passwd_base: invalid content path: /usr/share/base-passwd/group.master

Adding { mutable: true } option to the file in contents has no effect.

security: Upgrade golang.org/x/crypto to remediate CVEs

Chisel is being reported as containing High Risk Vulnerabilities do to the version of golang.org/x/crypto used in release v0.9.0.

image

golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
Chisel v0.9.0

Can this please be updated and published as a new release of Chisel?

error: cannot talk to archive

I have the latest release of chisel v0.9.0. Whenever I add the bash_config slice, I get an error cannot talk to archive.

$ ./chisel cut --root rootfs base-files_base base-files_release-info bash_config libstdc++6_libs
2024/03/08 15:15:37 Consulting release repository...
2024/03/08 15:15:38 Cached ubuntu-22.04 release is still up-to-date.
2024/03/08 15:15:38 Processing ubuntu-22.04 release...
2024/03/08 15:15:38 Selecting slices...
2024/03/08 15:15:38 Fetching ubuntu 22.04 jammy suite details...
2024/03/08 15:15:40 Release date: Thu, 21 Apr 2022 17:16:08 UTC
2024/03/08 15:15:40 Fetching index for ubuntu 22.04 jammy main component...
2024/03/08 15:15:40 Fetching index for ubuntu 22.04 jammy universe component...
2024/03/08 15:15:40 Fetching ubuntu 22.04 jammy-security suite details...
2024/03/08 15:15:41 Release date: Fri, 08 Mar 2024  4:18:30 UTC
2024/03/08 15:15:41 Fetching index for ubuntu 22.04 jammy-security main component...
2024/03/08 15:15:41 Fetching index for ubuntu 22.04 jammy-security universe component...
2024/03/08 15:15:41 Fetching ubuntu 22.04 jammy-updates suite details...
2024/03/08 15:15:41 Release date: Fri, 08 Mar 2024  4:19:31 UTC
2024/03/08 15:15:41 Fetching index for ubuntu 22.04 jammy-updates main component...
2024/03/08 15:15:41 Fetching index for ubuntu 22.04 jammy-updates universe component...
2024/03/08 15:15:41 Fetching pool/main/b/base-files/base-files_12ubuntu4.6_amd64.deb...
2024/03/08 15:15:41 Fetching pool/main/b/bash/bash_5.1-6ubuntu1_amd64.deb...
error: cannot talk to archive: Get "http://archive.ubuntu.com/ubuntu/dists/jammy/../../pool/main/b/bash/bash_5.1-6ubuntu1_amd64.deb": read tcp 10.0.2.15:40898->91.189.91.83:80: read: connection reset by peer

Is this something wrong with chisel? If I take out bash_config, it works fine.

$ ./chisel cut --root rootfs base-files_base base-files_release-info libstdc++6_libs
2024/03/08 15:15:18 Consulting release repository...
2024/03/08 15:15:19 Cached ubuntu-22.04 release is still up-to-date.
2024/03/08 15:15:19 Processing ubuntu-22.04 release...
2024/03/08 15:15:19 Selecting slices...
2024/03/08 15:15:19 Fetching ubuntu 22.04 jammy suite details...
2024/03/08 15:15:21 Release date: Thu, 21 Apr 2022 17:16:08 UTC
2024/03/08 15:15:21 Fetching index for ubuntu 22.04 jammy main component...
2024/03/08 15:15:21 Fetching index for ubuntu 22.04 jammy universe component...
2024/03/08 15:15:21 Fetching ubuntu 22.04 jammy-security suite details...
2024/03/08 15:15:21 Release date: Fri, 08 Mar 2024  4:18:30 UTC
2024/03/08 15:15:21 Fetching index for ubuntu 22.04 jammy-security main component...
2024/03/08 15:15:21 Fetching index for ubuntu 22.04 jammy-security universe component...
2024/03/08 15:15:21 Fetching ubuntu 22.04 jammy-updates suite details...
2024/03/08 15:15:22 Release date: Fri, 08 Mar 2024  4:19:31 UTC
2024/03/08 15:15:22 Fetching index for ubuntu 22.04 jammy-updates main component...
2024/03/08 15:15:22 Fetching index for ubuntu 22.04 jammy-updates universe component...
2024/03/08 15:15:22 Fetching pool/main/b/base-files/base-files_12ubuntu4.6_amd64.deb...
2024/03/08 15:15:22 Fetching pool/main/g/glibc/libc6_2.35-0ubuntu3.6_amd64.deb...
2024/03/08 15:15:22 Fetching pool/main/g/gcc-12/libgcc-s1_12.3.0-1ubuntu1~22.04_amd64.deb...
2024/03/08 15:15:22 Fetching pool/main/g/gcc-12/libstdc++6_12.3.0-1ubuntu1~22.04_amd64.deb...
2024/03/08 15:15:22 Extracting files from package "base-files"...
2024/03/08 15:15:22 Extracting files from package "libc6"...
2024/03/08 15:15:22 Extracting files from package "libgcc-s1"...
2024/03/08 15:15:22 Extracting files from package "libstdc++6"...

Support multiple archives

Currently chisel fetches only from single suite archive. For example for jammy, in sources.list that would look like:

deb http://archive.ubuntu.com/ubuntu/ jammy main universe

#19 adds support for -updates and -security but this is not general solution. We should follow Debian RFC822 control data format for sources[1] and instead specify archives in chisel.yaml like so:

format: chisel-v1

archives:
    ubuntu:
        uri: "http://archive.ubuntu.com/ubuntu/"
        suite: jammy
        components: [main, universe]
        arch: [amd64, i386]
    ubuntu-updates:
        uri: "http://archive.ubuntu.com/ubuntu/"
        suite: jammy-updates
        components: [main, universe]
        arch: [amd64, i386]
    ubuntu-security:
        uri: "http://archive.ubuntu.com/ubuntu/"
        suite: jammy-security
        components: [main, universe]
        arch: [amd64, i386]
    ubuntu-ports:
        uri: "http://ports.ubuntu.com/ubuntu-ports/"
        suite: jammy
        components: [main, universe]
        arch: [arm64, armhf, ppc64el, riscv64, s390x]
    ubuntu-ports-updates:
        uri: "http://ports.ubuntu.com/ubuntu-ports/"
        suite: jammy-updates
        components: [main, universe]
        arch: [arm64, armhf, ppc64el, riscv64, s390x]
    ubuntu-ports-security:
        uri: "http://ports.ubuntu.com/ubuntu-ports/"
        suite: jammy-security
        components: [main, universe]
        arch: [arm64, armhf, ppc64el, riscv64, s390x]

With this we will only have to change the parser and few references to release variables and keep most of archive.go as it is.

[1] https://manpages.debian.org/testing/apt/sources.list.5.en.html#DEB822-STYLE_FORMAT

Question: SBOM for chiseled Ubuntu images

I experimented with chisel and liked it, since I was able to create extremely small images based on Ubuntu 22.04.

But I also noticed, that SBOM generators (like https://github.com/anchore/syft) cannot detect packages installed inside the image (since there is no information in /var/lib/dpkg/status*). This will affect a multitude of security scanning tools, that rely on packaging information from dpkg (or apk or others) to detect if an component inside an image is affected by a security CVE.

Are there any plans to include a SBOM or packaging info into chiseled image, so that versions of the installed Ubuntu packages are not obfuscated?

e.g. in the distroless images, the deb packages info and the md5sums are placed into /var/lib/dpkg/status.d/*

Cannot create empty file (for mutable)

In order to write a file, it has to be listed in contents as mutable. However, if the file doesn't exist in package archive, it has to be created with non-empty text, otherwise chisel fails with no content error.

Demonstration:

$ cat slices/ca-certificates.yaml
package: ca-certificates
slices:
  data:
    contents:
      /etc/ssl/certs/ca-certificates.crt: { text: "", mutable: true }
    mutate: |
      content.write("/etc/ssl/certs/ca-certificates.crt", "foobar")
$ chisel cut --release $PWD --root ~/tmp/chisel-out ca-certificates_data
...
2022/06/10 09:07:38 Extracting files from package "ca-certificates"...
error: cannot extract from package "ca-certificates": no content at /etc/ssl/certs/ca-certificates.crt

It works when I explicitly set file text to non-empty value, e.g.:

$ cat slices/ca-certificates.yaml
package: ca-certificates
slices:
  data:
    contents:
      /etc/ssl/certs/ca-certificates.crt: { text: "todo", mutable: true }
    mutate: |
      content.write("/etc/ssl/certs/ca-certificates.crt", "foobar")

Guide on adding third-party packages to chiseled base image.

Our containers depend on the geoipupdate utility to keep the MaxMind database fresh on the container. How can we install the deb package in our image that is based on chiseled image? When we have the RUN dpkg -i /geoipupdate.deb in the docker file, we get a failed to solve: process "/bin/sh -c dpkg -i /geoipupdate.deb" did not complete successfully error during build. This also relies on cron to run the utility on a schedule.

Chisel v0.8 fails to install with `go install` command

Hi, I'm trying to install the chisel tool in my Docker image with the command recommended by the official GitHub release:

go install github.com/canonical/chisel/cmd/[email protected]

The simplest way to reproduce it is to run the following Docker command:

$ docker run --entrypoint /bin/sh --rm amd64/golang:1.20 -c "go install github.com/canonical/chisel/cmd/[email protected]"
go: github.com/canonical/chisel/cmd/[email protected]: no matching versions for query "v0.8"

It works with @latest and @<commit>.

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.