FiloSottile / mkcert

A simple zero-config tool to make locally trusted development certificates with any names you'd like.
https://mkcert.dev
BSD 3-Clause "New" or "Revised" License
48.82k stars 2.52k forks source link

Expose a library API #45

Open cretz opened 6 years ago

cretz commented 6 years ago

This is not usable as a library dependency for others via go get. Can keep the CLI iface the same, just pull the code into a package and expose things with reasonable names. Everything that can be done via the CLI should be doable programmatically (of course, the CLI code just invokes the lib code). I'd send a PR to do this myself, but I figure such a large refactor (as in size, not effort) should be done by the maintainer.

(also, during the refactoring, fix things like exposed internal variables, use of bool returns instead of error, etc)

FiloSottile commented 6 years ago

I had a couple people ask to use the tool as a library, so I'm going to leave this open to think about it, but it will take some care to design a simple API, and I think the ACME server will cover most of those use cases.

In the PR you mention not liking "hardcoding CA org name", but I definitely don't want mkcert to grow options, whether through the API or the CLI. It's a simple tool that serves a simple use case with no configuration. The API should not be different.

cretz commented 6 years ago

In the PR you mention not liking "hardcoding CA org name", but I definitely don't want mkcert to grow options, whether through the API or the CLI. It's a simple tool that serves a simple use case with no configuration. The API should not be different.

Mostly agree. That's why you have a set of options all with sensible defaults. So you take an options struct, or if nil you create use the default struct. In the struct you have something like a *x509.Certificate field for the template, and if it's not provided, you use the result from DefaultTemplate() which does have hardcoded values. It doesn't add as much code as it might sound. You don't have to expose a ton of options, but the option to change the cert template or at least refer to the default one is reasonable IMO. Also, IMO, the CLI should not accept all the options the API does.

If welcomed, I would be happy to do this refactoring, but just be advised that while the CLI args would look the exact same, the code might change considerably which is why I wouldn't go down this path without your blessing. Of course, when refactoring, I always attempt to remain extremely simple and do not over-abstract for the sake of over-abstracting and wouldn't even think of bringing in any dependencies.

adamdecaf commented 6 years ago

I think a library for add/delete operations on certificate stores should be a separate repository. Then this (and many others) project could use that.

What about starting with this interface?

type Store interface {
    Add(*x509.Certificate) error
    Remove(*x509.Certificate) error
}
cretz commented 6 years ago

@adamdecaf - Disagree a bit. Just a separate folder in this project would allow anyone else to use it. That interface also doesn't really have removal right. Darwin/Linux just remove a cert path, Windows removes based on subject org. And I think as an initial first step, just doing what mkcert does is enough. So more like:

type Mkcert struct {
    // ...
}

func (*Mkcert) Install() error   { panic("TODO: use currPlatform") }
func (*Mkcert) Uninstall() error { panic("TODO: use currPlatform") }

type platform interface {
    install(*Mkcert) error
    uninstall(*Mkcert) error
}

// then in platform_windows.go

var currPlatform platform = windowsPlatform{}

type windowsPlatform struct{}

func (windowsPlatform) install(m *Mkcert) error   { panic("TODO") }
func (windowsPlatform) uninstall(m *Mkcert) error { panic("TODO") }

Then a simple new(Mkcert).Install() does exactly what mkcert -install does from the CLI, yet Mkcert could have a couple of nil-means-fall-back-to-default fields (e.g. template, root path, etc).

If there is more value in abstracting how to communicate with OS-specific CAs, then that is a separate concern and probably a separate library, yes.

adamdecaf commented 6 years ago

Why can't org be pulled from the certificate? Would additional certificates be revoked/untrusted? I see in your windows PR the org value used is copied from the certificate.

https://github.com/FiloSottile/mkcert/blob/master/cert.go#L53

cretz commented 6 years ago

@adamdecaf - Answered in that PR.

mholt commented 6 years ago

I find myself having a need to use this tool as a library from my Go program. What can I do to help make this happen?

(Specifically, I need to install a root CA that is unique to that client machine, and then call a function and get back a *x509.Certificate for some given names and is good for some duration. Or a tls.Certificate -- doesn't matter too much. Could even be ASN bytes, just something that's not necessarily written to disk.)

FiloSottile commented 6 years ago

Hi Matt, thanks for helping with context! A couple questions:

mholt commented 6 years ago

do you need the CA to be installed in the system store, or just for it to be trusted by your Go program?

I need it to be trusted by the web browser(s). My Go program exposes a local static file server that needs to use HTTPS because scripts on the page require HTTPS. https://localhost:1234 is what we're going for.

would an ACME server work instead?

As long as its certificates are trusted in browsers, but an ACME server and client configuration does seem a little overkill for what I need.

(Perhaps writing the files to disk is OK too, if that's recommended -- as long as I can say where they go. Since Go's ListenAndServeTLS option takes filenames rather than bytes.)

FiloSottile commented 6 years ago

I am resisting adding an API because I don't want to encourage depending on mkcert (and hence on installing a root) as part of end-user software. Instead I want mkcert to be a drop-in replacement for a part of the process while testing or developing things. In that way, an ACME server makes sense, even one that you can start with an API. It's just replacing the endpoint you'd hit on prod. (And it works with Caddy!)

You could then use x/acme/autocert without a cache to avoid disk writes. Note that mkcert will write the CA to disk.

Can you tell me a bit more about what this is for? Maybe I have to accept that after all there is a use case in shippable software, but that would take me more examples and some thinking. (I'm having similar issues deciding if it makes sense to use mkcert for internal infrastructure, but the right answer there is probably minica and manual installs.)

(BTW, you can use ListenAndServeTLS with a tls.Certificate generated via tls.X509KeyPair by setting http.Server.TLSConfig. It's more obscure than it should be.)

mholt commented 6 years ago

In that way, an ACME server makes sense, even one that you can start with an API.

Yes, you're right -- I suppose I can integrate a small ACME client (like autocert, or lego) that hits a small ACME server (but then I need to find a good server that's production-ready but also minimal). However, the CA still has to be trusted by the browser.

Can you tell me a bit more about what this is for?

It's for the web UI for a Go application that will be distributed to numerous independent clients. As you know, I'm no fan of messing with root stores on client machines, but I think our use case here is legitimate. The web UI is accessible only via the loopback interface, so no network transmissions are even happening. However, certain scripts such as Stripe.js refuse to work over HTTP, even in secure localhost contexts (I've already emailed them about this). This means our locally-served web app can't let the customer set their payment card, for example, even though all network transmissions use HTTPS. 🤷‍♂️

So, in summary, this is for software for non-technical users. No TLS interception going on, no nefarious use of the root store or custom CA: just need to ensure that the root key is generated per-client (and stored safely) and not shipped with the application, of course. :)

I can understand if this is outside the use case of mkcert. Feel free to say so!

(BTW, you can use ListenAndServeTLS with a tls.Certificate generated via tls.X509KeyPair by setting http.Server.TLSConfig. It's more obscure than it should be.)

Yeah, we do this in Caddy. I just thought I'd mention it here since I don't know what the predominant use case will be for other users of this lib. I'm not sure I've even decided which method to use. :sweat_smile:

sanbornm commented 5 years ago

Thanks for making this project!

I also have a use case for separating the library from main:

Printing to thermal printers (shipping/receipt/name badges/etc) unfortunately requires raw access to send print codes which browsers don't support for obvious security reasons.

To get around this, the client can run a "print server" on http(s)://localhost:8080 and POST their print job to themselves via loopback which then sends the print job locally.

This workflow has worked great for years but recent changes in browsers and the number of HTTPS only sites has had an impact on this workflow. You can no longer effectively send an HTTP request to localhost if the site you are getting the print job from is HTTPS. Self-signed certs are also no longer accepted without modifying hidden browser settings.

To get around this we give our users instructions to create their own local CA and run through all the manual steps and arcane commands to create their own "legitimate" self-signed certs to provide to our client software. We would love to automate this across multiple OSes within our software.

The mkcert utility automatically does what we tell our users to do manually. We just want to include it in our app to streamline the process.

I believe not exposing a library along with the utility reduces the usefulness of this project even if it means some users abuse it. Separating the main utility from the library also provides better code organization and makes creating additional/future utilities easier. You can still warn users not to use the library without good cause if you are worried about abuse.

Because I need this change now, I'm going to fork and make it happen. I'll simply make a cmd/mkcert/main.go and separate calling funcs from flag/global state. Let me know if you want a PR.

I really don't want to maintain a fork so please consider. And, thanks again for all the hard work creating this!

Arteneko commented 5 years ago

I also have a use case for that, which is to manage a local-domain CA for simplified local-domain certificates generation over a custom interface.

The mkcert utility already generates CAs, so exposing an API would be awesome.

I don't really know how I could contribute to this feature though, any idea's gladly received.

octalmage commented 5 years ago

I've got a use-case for a mkcert library. I'm currently working on a local development environment for our WordPress clients, and I'd love to be able to use mkcert as a library in our go app to handle cert setup and installation. Thanks for the great tool!

maraino commented 5 years ago

@cretz @FiloSottile I also have a use case in a project that I'm working on, so I went ahead and created a package with the install/uninstall functionality. Here it is https://github.com/smallstep/truststore

PS: I haven't tested it in all the OSs, so feel free to fill an issue if this is not working for you.

brad-jones commented 5 years ago

I'd like to be able use this via my task runner https://github.com/brad-jones/gomake (plenty of other go based task runners that would benefit too).

The end goal being that a developer can just run some task like gomake up which runs mkcert -install, generates some certs, one for each of our micro-services, builds each of the micro-services, ensures each micro-service trusts the CA, finally executes the entire stack via docker-compose up.

Right now I am shelling out to the mkcert command to make this all happen.

noglitchyo commented 5 years ago

@FiloSottile Hello. First of all, thanks for this great project which helps lot of people around as it seems (and me by the same occasion).

I was wondering if mkcert was exposing any API, and found this thread. I know that issue is about a library API and not just an API exposed to the system but I thought it would be the best place to talk about this instead of opening an other issue.

From the previous comments, and some other issues that I read, I understand your vision about mkcert and what are the risks that you want to reduce.

Though, I think, as some others, that exposing an API for mkcert would greatly improve its usage.

So, as the previous comments in this thread, I would like to also expose a use-case that I am running into.

I am working on a project that I would like to make available through a Docker container. It exposes a web service reachable by clients only if it's through HTTPS and if the certificate is trusted. For testing purpose and development only, (worth to notice that it would be mainly for development), I would like to make the certificates generation automatic when the Docker stack starts.

So, my idea was: it would be cool to be able to call mkcert from the Docker container to get the certificates and get the whole set up and ready to go.

I think a schema is shorter and more explicit than long explanations to illustrate the idea:

test

For security concerns, from my point of view, the API MUST request an authorization token. It would be dangerous that any container/programs that you run who can reach the API could make some calls to an installed mkcert without the user even knowing/allowing it. This authorization token, which could be for example a JWT would carry the permissions and other useful information (expiration time, token requests left...).

On mkcert side, what would it mean?

mkcert -token example.com 56000

On client side, what would it mean? (I will use my use case)

What would be in your opinion the downsides with this approach? If you don't see any advantages, what would be for you a nice and simple alternative? Let me know what you think.

As @mholt said:

I can understand if this is outside the use case of mkcert. Feel free to say so!

maraino commented 5 years ago

@noglitchyo you might want to look into smallstep/certificates. You can use smallstep/cli to create a CSR and a JWT token, those will get validated by the CA and you'll get a signed certificate back.

You will first need to configure the CA (certificates) using step ca init, and run it in your environment. Then you can create a token using step ca token [-san <san>] <cn> and get the certificate with step ca certificate --token <token> <cn> <cert.crt> <cert.key>. On your terminal, you can skip the step ca token and use just step ca certificate, it will automatically generate the token. Note that a password is required to decrypt to private JWK used to sign the token, but you can pass it using --password-file. By default, the certificates are short-lived, 24h, you can renew them using step ca renew, but you can configure the CA to increase the validity of the certificates.

Disclosure: I'm involved in the development of those projects.

maraino commented 5 years ago

@noglitchyo I forgot that you can bootstrap your environment so you don't need to provide the root certificate or the ca url using step ca bootstrap

FiloSottile commented 5 years ago

@noglitchyo Thanks for the detailed comment. I see that as definitely out of scope for mkcert itself, and something that can be built on top of it. In particular because there are so many ways to do authorization and authentication that we can't implement them all well.

It should be fairly doable by shelling out to mkcert, as well.

icio commented 5 years ago

Thoroughly enjoying mkcert, and I was thinking about extending @unravelin's dev CLI to make use of it. Part of that CLI spins up proxies to remote running services on local ports, and I wanted to automate creating the certificates for the local environment in a way similar to how certmagic lets you for public. So I threw together https://godoc.org/github.com/icio/mkcert to programmatically invoke the CLI:

cert, err := mkcert.Exec(
    // Domains tells mkcert what certificate to generate.
    mkcert.Domains("localhost"),
    // RequireTrusted(true) tells Exec to return an error if the CA isn't
    // in the trust stores.
    mkcert.RequireTrusted(true),
    // CertFile and KeyFile override the default behaviour of generating
    // the keys in the local directory.
    mkcert.CertFile(filepath.Join(dir, "cert.pem")),
    mkcert.KeyFile(filepath.Join(dir, "key.pem")),
)
log.Fatal(http.ListenAndServeTLS(*bind, cert.File, cert.KeyFile, h))

Users still need to have mkcert installed, but updates to dev won't require us to announce to anybody they need to recreate certificates - we can just do it during startup.

mholt commented 5 years ago

@icio Since you mentioned it, mkcert would make an excellent certmagic.Manager implementation. This way you get all the same API as CertMagic for public certs but with private certs using mkcert under the hood. Basically you'd get the holy grail of

// serves handler on https://localhost with HTTP->HTTPS redirects
certmagic.HTTPS([]string{"localhost"}, handler)

to work with only one extra line:

// use mkcert to manage certificates instead of the default ACME manager
certmagic.Default.NewManager = MakeMkcertThing
cespare commented 5 years ago

Hi @FiloSottile,

My use case: at my company we use a private root CA for internal services. Right now we have a onboarding process that requires manually doing the stuff mkcert does to copy the cert into various trust stores. I'd like to replace this step with a tool that uses the same fiddly code mkcert already takes care of. This is especially important if we want to move to shorter-lived certificates.

This use case might be different from most of the ones mentioned on this thread since I don't want to issue new certificates at all, just reuse the copy-to-trust-store code.

I think I can use mkcert itself for this use case today: I'd have to make a temp CAROOT and copy my cert into there as rootCA.pem, then call mkcert -install. But it would be cleaner if I could call the Go code directly from my own tools.

(By the way, I was also thinking that maybe we're doing it wrong and we should use Let's Encrypt even for internal services. However, some investigation seems to indicate that it's not intended to be used for non-public domains. I'm happy to be told I'm doing it wrong, though.)

maraino commented 5 years ago

@cespare take a look to https://github.com/smallstep/truststore, it implements mkcert install logic but you can use it in your code.