-
Notifications
You must be signed in to change notification settings - Fork 40
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
Require credentials for non-container clients #2361
Changes from all commits
9661358
fae15d3
d470734
25ce086
8458296
f903eba
5709377
29d4193
979e9a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,7 +33,7 @@ | |
from ._utils.async_utils import TaskContext, synchronize_api | ||
from ._utils.grpc_utils import create_channel, retry_transient_errors | ||
from ._utils.http_utils import ClientSessionRegistry | ||
from .config import _check_config, config, logger | ||
from .config import _check_config, _is_remote, config, logger | ||
from .exception import AuthError, ClientClosed, ConnectionError, DeprecationError, VersionError | ||
|
||
HEARTBEAT_INTERVAL: float = config.get("heartbeat_interval") | ||
|
@@ -100,6 +100,7 @@ class _Client: | |
_cancellation_context: TaskContext | ||
_cancellation_context_event_loop: asyncio.AbstractEventLoop = None | ||
_stub: Optional[api_grpc.ModalClientStub] | ||
_credentials: Optional[Tuple[str, str]] | ||
|
||
def __init__( | ||
self, | ||
|
@@ -115,7 +116,6 @@ def __init__( | |
self.client_type = client_type | ||
self._credentials = credentials | ||
self.version = version | ||
self._authenticated = False | ||
self._closed = False | ||
self._channel: Optional[grpclib.client.Channel] = None | ||
self._stub: Optional[modal_api_grpc.ModalClientModal] = None | ||
|
@@ -127,7 +127,7 @@ def is_closed(self) -> bool: | |
|
||
@property | ||
def authenticated(self): | ||
return self._authenticated | ||
return self._credentials is not None | ||
|
||
@property | ||
def stub(self) -> modal_api_grpc.ModalClientModal: | ||
|
@@ -159,7 +159,7 @@ async def _close(self, prep_for_restore: bool = False): | |
# Remove cached client. | ||
self.set_env_client(None) | ||
|
||
async def _init(self): | ||
async def _hello(self): | ||
"""Connect to server and retrieve version information; raise appropriate error for various failures.""" | ||
logger.debug("Client: Starting") | ||
_check_config() | ||
|
@@ -174,7 +174,6 @@ async def _init(self): | |
if resp.warning: | ||
ALARM_EMOJI = chr(0x1F6A8) | ||
warnings.warn(f"{ALARM_EMOJI} {resp.warning} {ALARM_EMOJI}", DeprecationError) | ||
self._authenticated = True | ||
except GRPCError as exc: | ||
if exc.status == Status.FAILED_PRECONDITION: | ||
raise VersionError( | ||
|
@@ -191,7 +190,7 @@ async def _init(self): | |
async def __aenter__(self): | ||
await self._open() | ||
try: | ||
await self._init() | ||
await self._hello() | ||
except BaseException: | ||
await self._close() | ||
raise | ||
|
@@ -210,7 +209,7 @@ async def anonymous(cls, server_url: str) -> AsyncIterator["_Client"]: | |
client = cls(server_url, api_pb2.CLIENT_TYPE_CLIENT, credentials=None) | ||
try: | ||
await client._open() | ||
# Skip client._init | ||
# Skip client._hello | ||
yield client | ||
finally: | ||
await client._close() | ||
|
@@ -226,44 +225,36 @@ async def from_env(cls, _override_config=None) -> "_Client": | |
else: | ||
c = config | ||
|
||
server_url = c["server_url"] | ||
|
||
token_id = c["token_id"] | ||
token_secret = c["token_secret"] | ||
task_id = c["task_id"] | ||
credentials = None | ||
|
||
if task_id: | ||
client_type = api_pb2.CLIENT_TYPE_CONTAINER | ||
else: | ||
client_type = api_pb2.CLIENT_TYPE_CLIENT | ||
if token_id and token_secret: | ||
credentials = (token_id, token_secret) | ||
|
||
if cls._client_from_env_lock is None: | ||
cls._client_from_env_lock = asyncio.Lock() | ||
|
||
async with cls._client_from_env_lock: | ||
if cls._client_from_env: | ||
return cls._client_from_env | ||
|
||
server_url = c["server_url"] | ||
|
||
if _is_remote(): | ||
credentials = None | ||
client_type = api_pb2.CLIENT_TYPE_CONTAINER | ||
Comment on lines
+237
to
+239
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we're setting ourselves up for (more) confusion about the two distinct meanings of the local/remote dichotomy in Modal:
(This is maybe not a perfect set of definitions but hopefully it makes sense) The But what about users who want to run or deploy applications from Modal infrastructure? I thought we had people doing that; including people doing it on behalf of their users (such that the orchestrator app and worker app are using different tokens). Are we breaking that behavior? Maybe we're not — but it feels very hard to reason about. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me add a test for deploying Modal apps from inside containers. It's possible this PR broke it because of the |
||
else: | ||
client = _Client(server_url, client_type, credentials) | ||
await client._open() | ||
async_utils.on_shutdown(client._close()) | ||
try: | ||
await client._init() | ||
except AuthError: | ||
if not credentials: | ||
creds_missing_msg = ( | ||
"Token missing. Could not authenticate client." | ||
" If you have token credentials, see modal.com/docs/reference/modal.config for setup help." | ||
" If you are a new user, register an account at modal.com, then run `modal token new`." | ||
) | ||
raise AuthError(creds_missing_msg) | ||
else: | ||
raise | ||
cls._client_from_env = client | ||
return client | ||
token_id = c["token_id"] | ||
token_secret = c["token_secret"] | ||
if not token_id or not token_secret: | ||
raise AuthError( | ||
"Token missing. Could not authenticate client." | ||
" If you have token credentials, see modal.com/docs/reference/modal.config for setup help." | ||
" If you are a new user, register an account at modal.com, then run `modal token new`." | ||
) | ||
credentials = (token_id, token_secret) | ||
client_type = api_pb2.CLIENT_TYPE_CLIENT | ||
|
||
client = _Client(server_url, client_type, credentials) | ||
await client._open() | ||
async_utils.on_shutdown(client._close()) | ||
await client._hello() | ||
cls._client_from_env = client | ||
return client | ||
|
||
@classmethod | ||
async def from_credentials(cls, token_id: str, token_secret: str) -> "_Client": | ||
|
@@ -284,7 +275,7 @@ async def from_credentials(cls, token_id: str, token_secret: str) -> "_Client": | |
client = _Client(server_url, client_type, credentials) | ||
await client._open() | ||
try: | ||
await client._init() | ||
await client._hello() | ||
except BaseException: | ||
await client._close() | ||
raise | ||
|
@@ -345,7 +336,7 @@ async def _reset_on_pid_change(self): | |
self.set_env_client(None) | ||
# TODO(elias): reset _cancellation_context in case ? | ||
await self._open() | ||
# intentionally not doing self._init since we should already be authenticated etc. | ||
# intentionally not doing self._hello since we should already be authenticated etc. | ||
|
||
async def _get_grpclib_method(self, method_name: str) -> Any: | ||
# safely get grcplib method that is bound to a valid channel | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might just be hard to think about because there are many different ways of creating a Client, but this strikes me as wrong? Can't you end up with a Client that has credentials attached, but we don't know if they are valid? Not sure what the practical implications would be, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So yeah I think this is actually wrong (and let me try to add a test for it) but for a different reason I think. If a Modal app running in the cloud is trying to deploy a Modal app then I think it asserts that the client is authenticated, which will fail. Honestly think it's maybe better to remove this code altogether and just fail in respective handlers – I'm not sure why we need this check.