diff --git a/sdks/python/container/Dockerfile b/sdks/python/container/Dockerfile index 3bdef2dc1ddc..5a8e08e7f166 100644 --- a/sdks/python/container/Dockerfile +++ b/sdks/python/container/Dockerfile @@ -23,6 +23,7 @@ ARG TARGETOS ARG TARGETARCH COPY target/base_image_requirements.txt /tmp/base_image_requirements.txt +COPY target/license_scripts/upgrade_bundled_pip.py /tmp/upgrade_bundled_pip.py COPY target/apache-beam.tar.gz /opt/apache/beam/tars/ COPY target/launcher/${TARGETOS}_${TARGETARCH}/boot target/LICENSE target/NOTICE target/LICENSE.python /opt/apache/beam/ @@ -84,16 +85,20 @@ RUN \ # Remove pip cache. rm -rf /root/.cache/pip && \ - # Update ensurepip to use most recent versions of setuptools and pip. This avoids some vulnerabilities which won't be fixed on older versions of python. - pip install upgrade_ensurepip; \ - python3 -m upgrade_ensurepip; \ - # setuptools is not bundled with ensurepip in Python 3.12+ + # Update ensurepip bundled wheels to patched pip/setuptools (security). + # setuptools is not bundled with ensurepip in Python 3.12+; upgrade_ensurepip expects both. if [ "${py_version}" = "3.10" ] || [ "${py_version}" = "3.11" ]; then \ - find /usr/local/lib/python${py_version}/ensurepip/_bundled/setuptools-* -type f ! -name $(basename $(ls -v /usr/local/lib/python${py_version}/ensurepip/_bundled/setuptools-*-py3-none-any.whl | tail -n 1)) -delete; \ - fi; \ - find /usr/local/lib/python${py_version}/ensurepip/_bundled/pip-* -type f ! -name $(basename $(ls -v /usr/local/lib/python${py_version}/ensurepip/_bundled/pip-*-py3-none-any.whl | tail -n 1)) -delete; \ - pip uninstall upgrade_ensurepip -y; \ - python3 -m ensurepip; + pip install upgrade_ensurepip && \ + python3 -m upgrade_ensurepip && \ + find /usr/local/lib/python${py_version}/ensurepip/_bundled/setuptools-* -type f ! -name $(basename $(ls -v /usr/local/lib/python${py_version}/ensurepip/_bundled/setuptools-*-py3-none-any.whl | tail -n 1)) -delete && \ + find /usr/local/lib/python${py_version}/ensurepip/_bundled/pip-* -type f ! -name $(basename $(ls -v /usr/local/lib/python${py_version}/ensurepip/_bundled/pip-*-py3-none-any.whl | tail -n 1)) -delete && \ + pip uninstall upgrade_ensurepip -y && \ + python3 -m ensurepip; \ + else \ + python3 /tmp/upgrade_bundled_pip.py && \ + find /usr/local/lib/python${py_version}/ensurepip/_bundled/pip-* -type f ! -name $(basename $(ls -v /usr/local/lib/python${py_version}/ensurepip/_bundled/pip-*-py3-none-any.whl | tail -n 1)) -delete && \ + python3 -m ensurepip; \ + fi ENTRYPOINT ["/opt/apache/beam/boot"] diff --git a/sdks/python/container/license_scripts/upgrade_bundled_pip.py b/sdks/python/container/license_scripts/upgrade_bundled_pip.py new file mode 100644 index 000000000000..ca687f363ff8 --- /dev/null +++ b/sdks/python/container/license_scripts/upgrade_bundled_pip.py @@ -0,0 +1,65 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +Upgrade the pip wheel bundled in ensurepip for Python 3.12+. + +The script is executed within Docker after the image pip has been upgraded. +upgrade_ensurepip expects setuptools to be bundled as well, but CPython 3.12+ +only ships pip in ensurepip/_bundled. +""" + +import subprocess +import sys +from pathlib import Path + +import ensurepip + + +def main(): + ep_path = Path(ensurepip.__file__) + wheel_dir = ep_path.parent / '_bundled' + pip_version = subprocess.check_output( + [sys.executable, '-m', 'pip', '--version'], + text=True).split()[1] + subprocess.check_call([ + sys.executable, + '-m', + 'pip', + 'download', + 'pip=={}'.format(pip_version), + '-d', + str(wheel_dir), + '--no-deps', + ]) + lines = ep_path.read_text().splitlines() + pip_line = None + for idx, line in enumerate(lines): + if line.startswith('_PIP_VERSION = '): + pip_line = idx + break + if pip_line is None: + sys.exit('ensurepip _PIP_VERSION not found') + org = ep_path.with_suffix('.py.org') + if not org.exists(): + ep_path.rename(org) + lines[pip_line] = '_PIP_VERSION = "{}"'.format(pip_version) + ep_path.write_text('\n'.join(lines) + '\n') + + +if __name__ == '__main__': + main()