Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
| ... | ... |
@@ -584,15 +584,15 @@ func (c *Cluster) UnlockSwarm(req types.UnlockRequest) error {
|
| 584 | 584 |
n, err := c.startNewNode(config) |
| 585 | 585 |
if err != nil {
|
| 586 | 586 |
c.Unlock() |
| 587 |
- if errors.Cause(err) == ErrSwarmLocked {
|
|
| 588 |
- return errors.New("swarm could not be unlocked: invalid key provided")
|
|
| 589 |
- } |
|
| 590 | 587 |
return err |
| 591 | 588 |
} |
| 592 | 589 |
c.Unlock() |
| 593 | 590 |
select {
|
| 594 | 591 |
case <-n.Ready(): |
| 595 | 592 |
case <-n.done: |
| 593 |
+ if errors.Cause(c.err) == ErrSwarmLocked {
|
|
| 594 |
+ return errors.New("swarm could not be unlocked: invalid key provided")
|
|
| 595 |
+ } |
|
| 596 | 596 |
return fmt.Errorf("swarm component could not be started: %v", c.err)
|
| 597 | 597 |
} |
| 598 | 598 |
go c.reconnectOnFailure(n) |
| ... | ... |
@@ -881,6 +881,8 @@ func (c *Cluster) Info() types.Info {
|
| 881 | 881 |
info.LocalNodeState = types.LocalNodeStatePending |
| 882 | 882 |
if c.ready == true {
|
| 883 | 883 |
info.LocalNodeState = types.LocalNodeStateActive |
| 884 |
+ } else if c.locked {
|
|
| 885 |
+ info.LocalNodeState = types.LocalNodeStateLocked |
|
| 884 | 886 |
} |
| 885 | 887 |
} |
| 886 | 888 |
if c.err != nil {
|
| ... | ... |
@@ -855,56 +855,86 @@ func (s *DockerSwarmSuite) TestDNSConfigUpdate(c *check.C) {
|
| 855 | 855 |
func (s *DockerSwarmSuite) TestSwarmInitLocked(c *check.C) {
|
| 856 | 856 |
d := s.AddDaemon(c, false, false) |
| 857 | 857 |
|
| 858 |
- cmd := d.command("swarm", "init", "--lock-key")
|
|
| 859 |
- cmd.Stdin = bytes.NewBufferString("my-secret-key")
|
|
| 860 |
- out, err := cmd.CombinedOutput() |
|
| 861 |
- c.Assert(err, checker.IsNil, check.Commentf("out: %v", string(out)))
|
|
| 858 |
+ outs, err := d.Cmd("swarm", "init", "--autolock")
|
|
| 859 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs))
|
|
| 860 |
+ |
|
| 861 |
+ c.Assert(outs, checker.Contains, "docker swarm unlock") |
|
| 862 |
+ |
|
| 863 |
+ var unlockKey string |
|
| 864 |
+ for _, line := range strings.Split(outs, "\n") {
|
|
| 865 |
+ if strings.Contains(line, "SWMKEY") {
|
|
| 866 |
+ unlockKey = strings.TrimSpace(line) |
|
| 867 |
+ break |
|
| 868 |
+ } |
|
| 869 |
+ } |
|
| 870 |
+ |
|
| 871 |
+ c.Assert(unlockKey, checker.Not(checker.Equals), "") |
|
| 862 | 872 |
|
| 863 |
- c.Assert(string(out), checker.Contains, "docker swarm unlock") |
|
| 873 |
+ outs, err = d.Cmd("swarm", "unlock-key", "-q")
|
|
| 874 |
+ c.Assert(outs, checker.Equals, unlockKey+"\n") |
|
| 864 | 875 |
|
| 865 | 876 |
info, err := d.info() |
| 866 | 877 |
c.Assert(err, checker.IsNil) |
| 867 | 878 |
c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive) |
| 868 | 879 |
|
| 869 |
- c.Assert(d.Stop(), checker.IsNil) |
|
| 870 |
- c.Assert(d.Start(), checker.IsNil) |
|
| 880 |
+ c.Assert(d.Restart(), checker.IsNil) |
|
| 871 | 881 |
|
| 872 | 882 |
info, err = d.info() |
| 873 | 883 |
c.Assert(err, checker.IsNil) |
| 874 | 884 |
c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateLocked) |
| 875 | 885 |
|
| 876 |
- cmd = d.command("swarm", "unlock")
|
|
| 886 |
+ cmd := d.command("swarm", "unlock")
|
|
| 877 | 887 |
cmd.Stdin = bytes.NewBufferString("wrong-secret-key")
|
| 878 |
- out, err = cmd.CombinedOutput() |
|
| 888 |
+ out, err := cmd.CombinedOutput() |
|
| 879 | 889 |
c.Assert(err, checker.NotNil, check.Commentf("out: %v", string(out)))
|
| 880 | 890 |
c.Assert(string(out), checker.Contains, "invalid key") |
| 881 | 891 |
|
| 882 | 892 |
cmd = d.command("swarm", "unlock")
|
| 883 |
- cmd.Stdin = bytes.NewBufferString("my-secret-key")
|
|
| 893 |
+ cmd.Stdin = bytes.NewBufferString(unlockKey) |
|
| 884 | 894 |
out, err = cmd.CombinedOutput() |
| 885 | 895 |
c.Assert(err, checker.IsNil, check.Commentf("out: %v", string(out)))
|
| 886 | 896 |
|
| 887 | 897 |
info, err = d.info() |
| 888 | 898 |
c.Assert(err, checker.IsNil) |
| 889 | 899 |
c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive) |
| 900 |
+ |
|
| 901 |
+ outs, err = d.Cmd("node", "ls")
|
|
| 902 |
+ c.Assert(err, checker.IsNil) |
|
| 903 |
+ c.Assert(outs, checker.Not(checker.Contains), "Swarm is encrypted and needs to be unlocked") |
|
| 904 |
+ |
|
| 905 |
+ outs, err = d.Cmd("swarm", "update", "--autolock=false")
|
|
| 906 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs))
|
|
| 907 |
+ |
|
| 908 |
+ // Wait for autolock to be turned off |
|
| 909 |
+ time.Sleep(time.Second) |
|
| 910 |
+ |
|
| 911 |
+ c.Assert(d.Restart(), checker.IsNil) |
|
| 912 |
+ |
|
| 913 |
+ info, err = d.info() |
|
| 914 |
+ c.Assert(err, checker.IsNil) |
|
| 915 |
+ c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive) |
|
| 916 |
+ |
|
| 917 |
+ outs, err = d.Cmd("node", "ls")
|
|
| 918 |
+ c.Assert(err, checker.IsNil) |
|
| 919 |
+ c.Assert(outs, checker.Not(checker.Contains), "Swarm is encrypted and needs to be unlocked") |
|
| 890 | 920 |
} |
| 891 | 921 |
|
| 892 | 922 |
func (s *DockerSwarmSuite) TestSwarmLeaveLocked(c *check.C) {
|
| 893 | 923 |
d := s.AddDaemon(c, false, false) |
| 894 | 924 |
|
| 895 |
- cmd := d.command("swarm", "init", "--lock-key")
|
|
| 896 |
- cmd.Stdin = bytes.NewBufferString("foobar")
|
|
| 897 |
- out, err := cmd.CombinedOutput() |
|
| 898 |
- c.Assert(err, checker.IsNil, check.Commentf("out: %v", string(out)))
|
|
| 925 |
+ outs, err := d.Cmd("swarm", "init", "--autolock")
|
|
| 926 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs))
|
|
| 899 | 927 |
|
| 900 |
- c.Assert(d.Stop(), checker.IsNil) |
|
| 901 |
- c.Assert(d.Start(), checker.IsNil) |
|
| 928 |
+ c.Assert(d.Restart("--swarm-default-advertise-addr=lo"), checker.IsNil)
|
|
| 902 | 929 |
|
| 903 | 930 |
info, err := d.info() |
| 904 | 931 |
c.Assert(err, checker.IsNil) |
| 905 | 932 |
c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateLocked) |
| 906 | 933 |
|
| 907 |
- outs, err := d.Cmd("swarm", "leave", "--force")
|
|
| 934 |
+ outs, _ = d.Cmd("node", "ls")
|
|
| 935 |
+ c.Assert(outs, checker.Contains, "Swarm is encrypted and needs to be unlocked") |
|
| 936 |
+ |
|
| 937 |
+ outs, err = d.Cmd("swarm", "leave", "--force")
|
|
| 908 | 938 |
c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs))
|
| 909 | 939 |
|
| 910 | 940 |
info, err = d.info() |
| ... | ... |
@@ -918,3 +948,83 @@ func (s *DockerSwarmSuite) TestSwarmLeaveLocked(c *check.C) {
|
| 918 | 918 |
c.Assert(err, checker.IsNil) |
| 919 | 919 |
c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive) |
| 920 | 920 |
} |
| 921 |
+ |
|
| 922 |
+func (s *DockerSwarmSuite) TestSwarmRotateUnlockKey(c *check.C) {
|
|
| 923 |
+ d := s.AddDaemon(c, true, true) |
|
| 924 |
+ |
|
| 925 |
+ outs, err := d.Cmd("swarm", "update", "--autolock")
|
|
| 926 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs))
|
|
| 927 |
+ |
|
| 928 |
+ c.Assert(outs, checker.Contains, "docker swarm unlock") |
|
| 929 |
+ |
|
| 930 |
+ var unlockKey string |
|
| 931 |
+ for _, line := range strings.Split(outs, "\n") {
|
|
| 932 |
+ if strings.Contains(line, "SWMKEY") {
|
|
| 933 |
+ unlockKey = strings.TrimSpace(line) |
|
| 934 |
+ break |
|
| 935 |
+ } |
|
| 936 |
+ } |
|
| 937 |
+ |
|
| 938 |
+ c.Assert(unlockKey, checker.Not(checker.Equals), "") |
|
| 939 |
+ |
|
| 940 |
+ outs, err = d.Cmd("swarm", "unlock-key", "-q")
|
|
| 941 |
+ c.Assert(outs, checker.Equals, unlockKey+"\n") |
|
| 942 |
+ |
|
| 943 |
+ // Rotate multiple times |
|
| 944 |
+ for i := 0; i != 3; i++ {
|
|
| 945 |
+ outs, err = d.Cmd("swarm", "unlock-key", "-q", "--rotate")
|
|
| 946 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", outs))
|
|
| 947 |
+ // Strip \n |
|
| 948 |
+ newUnlockKey := outs[:len(outs)-1] |
|
| 949 |
+ c.Assert(newUnlockKey, checker.Not(checker.Equals), "") |
|
| 950 |
+ |
|
| 951 |
+ c.Assert(d.Restart(), checker.IsNil) |
|
| 952 |
+ |
|
| 953 |
+ info, err := d.info() |
|
| 954 |
+ c.Assert(err, checker.IsNil) |
|
| 955 |
+ c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateLocked) |
|
| 956 |
+ |
|
| 957 |
+ outs, _ = d.Cmd("node", "ls")
|
|
| 958 |
+ c.Assert(outs, checker.Contains, "Swarm is encrypted and needs to be unlocked") |
|
| 959 |
+ |
|
| 960 |
+ cmd := d.command("swarm", "unlock")
|
|
| 961 |
+ cmd.Stdin = bytes.NewBufferString(unlockKey) |
|
| 962 |
+ out, err := cmd.CombinedOutput() |
|
| 963 |
+ |
|
| 964 |
+ if err == nil {
|
|
| 965 |
+ // On occasion, the daemon may not have finished |
|
| 966 |
+ // rotating the KEK before restarting. The test is |
|
| 967 |
+ // intentionally written to explore this behavior. |
|
| 968 |
+ // When this happens, unlocking with the old key will |
|
| 969 |
+ // succeed. If we wait for the rotation to happen and |
|
| 970 |
+ // restart again, the new key should be required this |
|
| 971 |
+ // time. |
|
| 972 |
+ |
|
| 973 |
+ time.Sleep(3 * time.Second) |
|
| 974 |
+ |
|
| 975 |
+ c.Assert(d.Restart(), checker.IsNil) |
|
| 976 |
+ |
|
| 977 |
+ cmd = d.command("swarm", "unlock")
|
|
| 978 |
+ cmd.Stdin = bytes.NewBufferString(unlockKey) |
|
| 979 |
+ out, err = cmd.CombinedOutput() |
|
| 980 |
+ } |
|
| 981 |
+ c.Assert(err, checker.NotNil, check.Commentf("out: %v", string(out)))
|
|
| 982 |
+ c.Assert(string(out), checker.Contains, "invalid key") |
|
| 983 |
+ |
|
| 984 |
+ outs, _ = d.Cmd("node", "ls")
|
|
| 985 |
+ c.Assert(outs, checker.Contains, "Swarm is encrypted and needs to be unlocked") |
|
| 986 |
+ |
|
| 987 |
+ cmd = d.command("swarm", "unlock")
|
|
| 988 |
+ cmd.Stdin = bytes.NewBufferString(newUnlockKey) |
|
| 989 |
+ out, err = cmd.CombinedOutput() |
|
| 990 |
+ c.Assert(err, checker.IsNil, check.Commentf("out: %v", string(out)))
|
|
| 991 |
+ |
|
| 992 |
+ info, err = d.info() |
|
| 993 |
+ c.Assert(err, checker.IsNil) |
|
| 994 |
+ c.Assert(info.LocalNodeState, checker.Equals, swarm.LocalNodeStateActive) |
|
| 995 |
+ |
|
| 996 |
+ outs, err = d.Cmd("node", "ls")
|
|
| 997 |
+ c.Assert(err, checker.IsNil) |
|
| 998 |
+ c.Assert(outs, checker.Not(checker.Contains), "Swarm is encrypted and needs to be unlocked") |
|
| 999 |
+ } |
|
| 1000 |
+} |