@@ -9,8 +9,10 @@ import stream from 'stream';
99import timers from 'timers' ;
1010import util from 'util' ;
1111
12+ import Electron from 'electron' ;
1213import fetch from 'node-fetch' ;
1314import XDGAppPaths from 'xdg-app-paths' ;
15+ import { exec as sudo } from 'sudo-prompt' ;
1416
1517import { Settings } from '../config/settings' ;
1618import resources from '../resources' ;
@@ -19,6 +21,8 @@ import * as K8s from './k8s';
1921import K3sHelper from './k3sHelper' ;
2022
2123const paths = XDGAppPaths ( 'rancher-desktop' ) ;
24+ /** The GID of the 'admin' group on macOS */
25+ const adminGroup = 80 ;
2226
2327/**
2428 * The possible states for the docker-machine driver.
@@ -135,6 +139,44 @@ export default class HyperkitBackend extends events.EventEmitter implements K8s.
135139 return this . dockerMachineConfig . then ( v => v ?. Driver . Memory ?? 0 ) ;
136140 }
137141
142+ /**
143+ * Ensure that Hyperkit and associated binaries have the correct owner / is
144+ * set as suid.
145+ */
146+ protected async ensureHyperkitOwnership ( ) {
147+ const commands = [ ] ;
148+ // Check that the hyperkit driver is owned by root
149+ const { driver : driverExecutable } = this . hyperkitArgs ;
150+ const { uid, mode } = await fs . promises . stat ( driverExecutable ) ;
151+
152+ if ( uid !== 0 ) {
153+ commands . push ( `chown root "${ driverExecutable } "` ) ;
154+ }
155+ if ( ( mode & 0o4000 ) === 0 ) {
156+ commands . push ( `chmod u+s "${ driverExecutable } "` ) ;
157+ }
158+
159+ // Check that the hyperkit binary is in the 'admin' group
160+ const hyperkitExecutable = resources . executable ( 'hyperkit' ) ;
161+ const { gid : hyperkitGid } = await fs . promises . stat ( hyperkitExecutable ) ;
162+
163+ if ( hyperkitGid !== adminGroup ) {
164+ commands . push ( `chown :admin "${ hyperkitExecutable } "` ) ;
165+ }
166+
167+ if ( commands . length > 0 ) {
168+ const command = `sh -c '${ commands . join ( ' && ' ) } '` ;
169+ const options = { name : Electron . app . name } ;
170+
171+ console . log ( command ) ;
172+ await new Promise < void > ( ( resolve , reject ) => {
173+ sudo ( command , options , ( error , stdout , stderr ) => {
174+ return error ? reject ( error ) : resolve ( ) ;
175+ } ) ;
176+ } ) ;
177+ }
178+ }
179+
138180 protected get hyperkitArgs ( ) : { driver : string , defaultArgs : string [ ] } {
139181 return {
140182 driver : resources . executable ( 'docker-machine-driver-hyperkit' ) ,
@@ -291,6 +333,7 @@ export default class HyperkitBackend extends events.EventEmitter implements K8s.
291333
292334 await Promise . all ( [
293335 this . ensureImage ( ) ,
336+ this . ensureHyperkitOwnership ( ) ,
294337 this . k3sHelper . ensureK3sImages ( desiredVersion ) ,
295338 ] ) ;
296339
@@ -374,6 +417,7 @@ export default class HyperkitBackend extends events.EventEmitter implements K8s.
374417 async stop ( ) : Promise < number > {
375418 try {
376419 this . setState ( K8s . State . STOPPING ) ;
420+ await this . ensureHyperkitOwnership ( ) ;
377421 this . process ?. kill ( 'SIGTERM' ) ;
378422 if ( await this . vmState === DockerMachineDriverState . Running ) {
379423 await this . hyperkit ( 'stop' ) ;
0 commit comments