-
Notifications
You must be signed in to change notification settings - Fork 3
/
TimeMachine.groovy
207 lines (167 loc) · 7.96 KB
/
TimeMachine.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package com.eficode.devstack.util
import com.eficode.devstack.container.Container
import com.eficode.shaded.kong.unirest.Empty
import de.gesellix.docker.remote.api.ContainerCreateRequest
import kong.unirest.HttpResponse
import kong.unirest.JsonResponse
import kong.unirest.Unirest
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.text.SimpleDateFormat
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.temporal.TemporalAdjuster
import kong.unirest.UnirestInstance
/**
* <b>WARNING THIS AFFECTS ALL CONTAINERS</b>
* <p>
* This utility class is intended to change the "current time" as experienced by Docker Containers.
* It has only been tested on, and will likely only work in the official "MacOS Docker Desktop" app.
* <p>
* The class essentially performs privilege escalation exploit to run root privileged commands on the docker engine,
* <p>
* <b>NEVER EVER</b> use this class on a production docker engine
* <p>
* <b>NOTE</b> all containers sharing a Docker Engine also share the date/time, be aware that when changing time
* you change it for all running containers
*
*/
class TimeMachine implements Container {
String containerName = "TimeMachine"
String containerMainPort = null
String containerImage = "alpine"
String containerImageTag = "latest"
String defaultShell = "/bin/sh"
TimeMachine(String dockerHost = "", String dockerCertPath = "") {
if (dockerHost && dockerCertPath) {
assert setupSecureRemoteConnection(dockerHost, dockerCertPath): "Error setting up secure remote docker connection"
}
}
/**
* Travel back to the present
* <b>NEVER EVER</b> use this class on a production docker engine <p>
* <b>WARNING THIS AFFECTS ALL CONTAINERS - READ CLASS DOCUMENTATION</b>
* @param dockerHost optional
* @param dockerCertPath optional
* @return true after verifying success
*/
static boolean travelToNow(String dockerHost = "", String dockerCertPath = "", boolean useExternalSource = true) {
if(useExternalSource){
long timeFromExternal = getExternalTime()
return setTime(timeFromExternal, dockerHost, dockerCertPath)
} else {
return setTime(System.currentTimeSeconds(), dockerHost, dockerCertPath)
}
}
/**
* Travel X days in time from actual "Now"
* <b>NEVER EVER</b> use this class on a production docker engine <p>
* <b>WARNING THIS AFFECTS ALL CONTAINERS - READ CLASS DOCUMENTATION</b>
* @param days Number of days to travel, can be negative (to the past) or positive (to the future)
* @param dockerHost optional
* @param dockerCertPath optional
* @return true after verifying success
*/
static boolean travelDays(int days, String dockerHost = "", String dockerCertPath = "") {
long newEpochS = System.currentTimeSeconds() + Duration.ofDays(days).toSeconds()
return setTime(newEpochS, dockerHost, dockerCertPath)
}
/**
* Travel X days in time relative to
* <b>NEVER EVER</b> use this class on a production docker engine <p>
* <b>WARNING THIS AFFECTS ALL CONTAINERS - READ CLASS DOCUMENTATION</b>
* @param days Number of days to travel, can be negative (to the past) or positive (to the future)
* @param dockerHost optional
* @param dockerCertPath optional
* @return true after verifying success
*/
static boolean travelRelativeDays(int days, String dockerHost = "", String dockerCertPath = "") {
long currentDockerTime = getDockerTime(dockerHost, dockerCertPath)
long newEpochS = currentDockerTime + Duration.ofDays(days).toSeconds()
return setTime(newEpochS, dockerHost, dockerCertPath)
}
/**
* Set new time based on Date object
* <b>NEVER EVER</b> use this class on a production docker engine <p>
* <b>WARNING THIS AFFECTS ALL CONTAINERS - READ CLASS DOCUMENTATION</b>
* @param date A date object to use as the new "now"
* @param dockerHost optional
* @param dockerCertPath optional
* @return true after verifying success
*/
static boolean setDate(Date date, String dockerHost = "", String dockerCertPath = "") {
return setTime(((date.toInstant().toEpochMilli()) / 1000).toInteger(), dockerHost, dockerCertPath)
}
/**
* Set new time based on LocalDateTime object, uses the system default Time Zone
* <b>NEVER EVER</b> use this class on a production docker engine <p>
* <b>WARNING THIS AFFECTS ALL CONTAINERS - READ CLASS DOCUMENTATION</b>
* @param localDateTime A LocalDateTime object to use as the new "now"
* @param dockerHost optional
* @param dockerCertPath optional
* @return true after verifying success
*/
static boolean setLocalDateTime(LocalDateTime localDateTime, String dockerHost = "", String dockerCertPath = "") {
return setTime(localDateTime.toEpochSecond(ZoneId.systemDefault().offset), dockerHost, dockerCertPath)
}
/**
* Set new time based on LocalDate object
* <b>NEVER EVER</b> use this class on a production docker engine <p>
* <b>WARNING THIS AFFECTS ALL CONTAINERS - READ CLASS DOCUMENTATION</b>
* @param LocalDate A LocalDate object to use as the new "now"
* @param dockerHost optional
* @param dockerCertPath optional
* @return true after verifying success
*/
static boolean setLocalDate(LocalDate localDate, String dockerHost = "", String dockerCertPath = "") {
return setTime(localDate.toEpochDay(), dockerHost, dockerCertPath)
}
/**
* Change docker engine time <p>
* <b>NEVER EVER</b> use this class on a production docker engine <p>
* <b>WARNING THIS AFFECTS ALL CONTAINERS - READ CLASS DOCUMENTATION</b>
* @param epochS The new epoch in seconds to be used
* @param dockerHost optional
* @param dockerCertPath optional
* @return true after verifying success
*/
static boolean setTime(long epochS, String dockerHost = "", String dockerCertPath = "") {
Logger log = LoggerFactory.getLogger(this.class)
log.info("Setting global docker time to:" + epochS)
log.warn("THIS WILL AFFECT ALL CONTAINERS RUN BY THIS DOCKER ENGINE")
assert epochS <= 9999999999 && epochS > 1000000000: "Provide timestamp in epoch seconds"
ArrayList<String> cmdOut = runCmdAndRm(["nsenter", "-t", "1", "-m", "-u", "-n", "-i", "sh", "-c", "pkill sntpc || date -s \"@${epochS}\" && echo Status \$?"], 5000, [], dockerHost, dockerCertPath)
assert cmdOut.toString().contains("Status 0"): "Error setting time"
long newTime = getDockerTime(dockerHost, dockerCertPath)
assert newTime >= epochS: "The newly set time appears incorrect: " + newTime
return true
}
static long getExternalTime(){
HttpResponse<Empty> response = Unirest.get("http://google.com").asEmpty() as HttpResponse<Empty>
String dateString = response.headers["Date"].first()
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z")
Date date = dateFormat.parse(dateString)
return (date.time / 1000).round()
}
/**
* Get current time as reported by a docker container
* @param dockerHost
* @param dockerCertPath
* @return Epoch Seconds
*/
static long getDockerTime(String dockerHost = "", String dockerCertPath = "") {
ArrayList<String> cmdOut = runCmdAndRm('date +"%s"', 5000, [], dockerHost, dockerCertPath)
long timeStamp = cmdOut.find { it.isNumber() }?.toLong() ?: 0
assert timeStamp: "Unexpected output when getting docker time"
return timeStamp
}
@Override
ContainerCreateRequest customizeContainerCreateRequest(ContainerCreateRequest containerCreateRequest) {
containerCreateRequest.hostConfig.setPrivileged(true)
containerCreateRequest.hostConfig.setPidMode("host".toString())
return containerCreateRequest
}
}