Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement DNS-based mirror bootstrap protocol #3947

Merged
merged 2 commits into from
Oct 11, 2016

Conversation

hvr
Copy link
Member

@hvr hvr commented Oct 7, 2016

This way cabal can bootstrap secure repos even if the primary Hackage
instance is currently unreachable, as long as there's at least one
reachable and working secure mirror available.

NB: This new code-path is only used for the initial bootstrap. Once the
repository cache has been bootstrapped, its @mirrors.json@ meta-data is
used instead.

See also haskell/hackage-security#171

@hvr hvr added this to the 2.0 milestone Oct 7, 2016
@mention-bot
Copy link

@hvr, thanks for your PR! By analyzing the history of the files in this pull request, we identified @edsko, @dcoutts and @ezyang to be potential reviewers.

@hvr
Copy link
Member Author

hvr commented Oct 7, 2016

For reference, here's how cabal update -v looks for the bootstrap-case:

$ ./dist-newstyle/build/x86_64-linux/ghc-8.0.1/cabal-install-1.25.0.0/c/cabal/build/cabal/cabal update -v
/usr/bin/nslookup '-query=TXT' _mirrors.hackage.haskell.org
located 2 mirrors for http://hackage.haskell.org/ :
- http://hackage.fpcomplete.com/
- http://objects-us-west-1.dream.io/hackage-mirror/
Selected mirror http://hackage.haskell.org/
Downloading root
/usr/bin/curl 'http://hackage.haskell.org/root.json' --output /tmp/transportAdapterGet16816927771714636915 --location --write-out '%{http_code}' --user-agent 'cabal-install/1.25.0.0 (linux; x86_64)' --silent --show-error --dump-header /tmp/curl-headers1957747793424238335.txt --header 'Cache-Control: no-transform'
Downloading the latest package list from hackage.haskell.org
Selected mirror http://hackage.haskell.org/
Downloading timestamp
/usr/bin/curl 'http://hackage.haskell.org/timestamp.json' --output /tmp/transportAdapterGet5965166491189641421 --location --write-out '%{http_code}' --user-agent 'cabal-install/1.25.0.0 (linux; x86_64)' --silent --show-error --dump-header /tmp/curl-headers10252023621350490027.txt --header 'Cache-Control: no-transform'
Downloading snapshot
/usr/bin/curl 'http://hackage.haskell.org/snapshot.json' --output /tmp/transportAdapterGet20448977631967513926 --location --write-out '%{http_code}' --user-agent 'cabal-install/1.25.0.0 (linux; x86_64)' --silent --show-error --dump-header /tmp/curl-headers13651805401540383426.txt --header 'Cache-Control: no-transform'
Downloading mirrors
/usr/bin/curl 'http://hackage.haskell.org/mirrors.json' --output /tmp/transportAdapterGet35005211521595368 --location --write-out '%{http_code}' --user-agent 'cabal-install/1.25.0.0 (linux; x86_64)' --silent --show-error --dump-header /tmp/curl-headers2947025671726956429.txt --header 'Cache-Control: no-transform'
Updating index
/usr/bin/curl 'http://hackage.haskell.org/01-index.tar.gz' --output /tmp/transportAdapterGet278722862233665123 --location --write-out '%{http_code}' --user-agent 'cabal-install/1.25.0.0 (linux; x86_64)' --silent --show-error --dump-header /tmp/curl-headers2145174067468703135.txt --header 'Cache-Control: no-transform' --header 'Range: bytes=52328178-52394862'
Updating index cache file
/home/hvr/.cabal/packages/hackage.haskell.org/01-index.cache ...
Index cache updated to index-state 2016-10-07T20:57:08Z

@@ -0,0 +1,134 @@
module Distribution.Client.Security.DNS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't hackage-security a more appropriate place for this functionality?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@23Skidoo Well, @dcoutts asked me to implement it in cabal-install for the time being...

@hvr hvr force-pushed the pr/mirrors-bootstrap branch 3 times, most recently from 75dbee7 to d23b1e3 Compare October 8, 2016 13:34
where
ns = take (length s - 8) s

-- | Parse output of @nslookup -query=TXT $HOSTNAME@ tolerantly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you really sure you don't want to use a parser combinator for this? ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, what's the benefit? :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...well, ideally you would have written it with parser combinators to begin with :P

--
queryBootstrapMirrors :: Verbosity -> URI -> IO [URI]
queryBootstrapMirrors verbosity repoUri
| uriScheme repoUri `elem` ["http:","https:"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this test. Whether or not the hostname component of a URI is DNS resolvable has nothing to do with the scheme; e.g., ftp also does DNS. file is not a good excuse because these have a blank hostname, e.g. file:///foo (that's why there's three slashes!)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...what kind of test are you suggesting instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test if uriAuthority is Just.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

evaluate (force $ extractMirrors mirrorsDnsName out)

mirrors <- case mirrors' of
Left e -> (e::SomeException) `seq` return []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you don't want to warn in this case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I do so implicitly when null mirrors a few lines below... or do you want to see e displayed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to see e. If the code is doing something weird that e could have critical information for debugging.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

requiresBootstrap <- Sec.requiresBootstrap r
requiresBootstrap <- withRepo [] Sec.requiresBootstrap

mirrors <- if requiresBootstrap
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems worth mentioning (in info) that we're querying DNS to get some mirrors. And it'll only happen once.

Copy link
Member Author

@hvr hvr Oct 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered that, but I didn't want to have to duplicate the detection in queryBootstrapMirrors for when a DNS query is going to occur at all...

So what about:

if requiresBootstrap
then do
  info verbosity $ "Trying to locate mirrors via DNS for initial bootstrap of secure repository (" ++ show remoteRepoURI ++ ") ..."
  Sec.DNS.queryBootstrapMirrors verbosity remoteRepoURI
else ...

?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is what I had in mind.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@ezyang
Copy link
Contributor

ezyang commented Oct 9, 2016

Seems reasonable.

@23Skidoo
Copy link
Member

23Skidoo commented Oct 9, 2016

I'm absolutely not an expert on these matters, but can this be vulnerable to DNS spoofing/cache poisoning or some similar attack?

@hvr
Copy link
Member Author

hvr commented Oct 9, 2016

@23Skidoo we don't rely on DNS to be trustworthy; the trust is established via the signatures inside .json meta-data we retrieve. We already have this problem with the DNS entry for hackage.haskell.org which could be compromised, so relying on a 2nd one _mirrors.hackage.haskell.org in the same SOA doesn't make it worse IMO. I'll add a comment to that effect in the code (as discussed on IRC)

This way `cabal` can bootstrap secure repos even if the primary Hackage
instance is currently unreachable, as long as there's at least one
reachable and working secure mirror available.

NB: This new code-path is only used for the initial bootstrap. Once the
repository cache has been bootstrapped, its `mirrors.json` meta-data is
used instead.

See also haskell/hackage-security#171
@hvr hvr force-pushed the pr/mirrors-bootstrap branch 2 times, most recently from 176ff86 to ae24c5c Compare October 10, 2016 06:35
@23Skidoo
Copy link
Member

/cc @edsko

@edsko
Copy link
Contributor

edsko commented Oct 10, 2016

Commenting on the idea, not on the code: it seems reasonable to me. Like @hvr says, we don't rely on DNS information to be accurate to guard against attacks; as long as we get pointed to a server and that server has metadata signed by the root keys we expect, we're fine (of course, the distribution of those root keys is a different matter but that's orthogonal to this PR).

@dcoutts
Copy link
Contributor

dcoutts commented Oct 11, 2016

I havn't reviewed the code yet, but I concur with edsko about the security.

@23Skidoo 23Skidoo merged commit 34eecf4 into haskell:master Oct 11, 2016
@23Skidoo
Copy link
Member

Merged, thanks!

@hvr hvr deleted the pr/mirrors-bootstrap branch August 10, 2017 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants